blob: ce7e4f8fc70da1a35d0389b8c834f4a5b13fdfc1 [file] [log] [blame]
// Copyright (c) 2021, 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.ir.desugar.itf;
import com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
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.DexMethodHandle;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.MethodAccessFlags;
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.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugarDescription;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
//
// Default and static interface method desugaring rewriter (note that lambda
// desugaring should have already processed the code before this rewriter).
//
// In short, during default and static interface method desugaring
// the following actions are performed:
//
// (1) All static interface methods are moved into companion classes. All calls
// to these methods are redirected appropriately. All references to these
// methods from method handles are reported as errors.
//
// Companion class is a synthesized class (<interface-name>-CC) created to host
// static and former default interface methods (see below) from the interface.
//
// (2) All default interface methods are made static and moved into companion
// class.
//
// (3) All calls to default interface methods made via 'super' are changed
// to directly call appropriate static methods in companion classes.
//
// (4) All other calls or references to default interface methods are not changed.
//
// (5) For all program classes explicitly implementing interfaces we analyze the
// set of default interface methods missing and add them, the created methods
// forward the call to an appropriate method in interface companion class.
//
public final class InterfaceMethodRewriter implements CfInstructionDesugaring {
private final AppView<?> appView;
private final InternalOptions options;
final DexItemFactory factory;
private final InterfaceDesugaringSyntheticHelper helper;
// The emulatedMethod set is there to avoid doing the emulated look-up too often.
private final Set<DexString> emulatedMethods = Sets.newIdentityHashSet();
// All forwarding methods and all throwing methods generated during desugaring.
private final ProgramMethodSet synthesizedMethods = ProgramMethodSet.createConcurrent();
// Caches default interface method info for already processed interfaces.
private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
// This is used to filter out double desugaring on backported methods.
private final Set<CfInstructionDesugaring> precedingDesugarings;
/** Defines a minor variation in desugaring. */
public enum Flavor {
/** Process all application resources. */
IncludeAllResources,
/** Process all but DEX application resources. */
ExcludeDexResources
}
public InterfaceMethodRewriter(
AppView<?> appView, Set<CfInstructionDesugaring> precedingDesugarings) {
this.appView = appView;
this.precedingDesugarings = precedingDesugarings;
this.options = appView.options();
this.factory = appView.dexItemFactory();
this.helper = new InterfaceDesugaringSyntheticHelper(appView);
initializeEmulatedInterfaceVariables();
}
public static void checkForAssumedLibraryTypes(AppInfo appInfo, InternalOptions options) {
MachineDesugaredLibrarySpecification machineDesugaredLibrarySpecification =
options.machineDesugaredLibrarySpecification;
machineDesugaredLibrarySpecification
.getEmulatedInterfaces()
.forEach(
(ei, descriptor) -> {
registerType(appInfo, ei);
registerType(appInfo, descriptor.getRewrittenType());
});
machineDesugaredLibrarySpecification
.getCustomConversions()
.forEach(
(type, descriptor) -> {
registerType(appInfo, type);
registerType(appInfo, descriptor.getTo().getHolderType());
registerType(appInfo, descriptor.getFrom().getHolderType());
});
}
private static void registerType(AppInfo appInfo, DexType type) {
appInfo.dexItemFactory().registerTypeNeededForDesugaring(type);
DexClass clazz = appInfo.definitionFor(type);
if (clazz != null && clazz.isLibraryClass() && clazz.isInterface()) {
clazz.forEachMethod(
m -> {
if (m.isDefaultMethod()) {
appInfo
.dexItemFactory()
.registerTypeNeededForDesugaring(m.getReference().proto.returnType);
for (DexType param : m.getReference().proto.parameters.values) {
appInfo.dexItemFactory().registerTypeNeededForDesugaring(param);
}
}
});
}
}
public Set<DexString> getEmulatedMethods() {
return emulatedMethods;
}
private void initializeEmulatedInterfaceVariables() {
Set<DexType> emulateLibraryInterface =
options.machineDesugaredLibrarySpecification.getEmulatedInterfaces().keySet();
for (DexType interfaceType : emulateLibraryInterface) {
DexClass emulatedInterfaceClass = appView.definitionFor(interfaceType);
if (emulatedInterfaceClass != null) {
for (DexEncodedMethod encodedMethod :
emulatedInterfaceClass.methods(DexEncodedMethod::isDefaultMethod)) {
emulatedMethods.add(encodedMethod.getReference().name);
}
}
}
}
private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
return Iterables.any(
precedingDesugarings, desugaring -> desugaring.needsDesugaring(invoke, context));
}
@Override
public boolean hasPreciseNeedsDesugaring() {
return false;
}
/**
* If the method is not required to be desugared, scanning is used to upgrade when required the
* class file version, as well as reporting missing type.
*/
@Override
public void scan(ProgramMethod context, CfInstructionDesugaringEventConsumer eventConsumer) {
if (isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
leavingStaticInvokeToInterface(context);
return;
}
CfCode code = context.getDefinition().getCode().asCfCode();
for (CfInstruction instruction : code.getInstructions()) {
if (instruction.isInvokeDynamic()
&& !LambdaInstructionDesugaring.isLambdaInvoke(instruction, context, appView)
&& !StringConcatInstructionDesugaring.isStringConcatInvoke(
instruction, appView.dexItemFactory())) {
reportInterfaceMethodHandleCallSite(instruction.asInvokeDynamic().getCallSite(), context);
}
computeDescription(instruction, context).scan();
}
}
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
if (isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
return false;
}
return computeDescription(instruction, context).needsDesugaring();
}
@Override
public Collection<CfInstruction> desugarInstruction(
CfInstruction instruction,
FreshLocalProvider freshLocalProvider,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext,
CfInstructionDesugaringCollection desugaringCollection,
DexItemFactory dexItemFactory) {
assert !isSyntheticMethodThatShouldNotBeDoubleProcessed(context);
return computeDescription(instruction, context)
.desugarInstruction(
freshLocalProvider,
localStackAllocator,
eventConsumer,
context,
methodProcessingContext,
dexItemFactory);
}
private DesugarDescription computeDescription(CfInstruction instruction, ProgramMethod context) {
// Interface desugaring is only interested in invokes.
CfInvoke invoke = instruction.asInvoke();
if (invoke == null) {
return DesugarDescription.nothing();
}
// Don't desugar if the invoke is to be desugared by preceeding desugar tasks.
if (isAlreadyDesugared(invoke, context)) {
return DesugarDescription.nothing();
}
// There should never be any calls to interface initializers.
if (invoke.isInvokeSpecial() && invoke.isInvokeConstructor(factory)) {
return DesugarDescription.nothing();
}
// If the invoke is not an interface invoke, then there should generally not be any desugaring.
// However, there are some cases where the insertion of forwarding methods can change behavior
// so we need to identify them at the various call sites here.
if (!invoke.isInterface()) {
return computeNonInterfaceInvoke(invoke, context);
}
// If the target holder does not resolve we may want to issue diagnostics.
DexClass holder = appView.definitionForHolder(invoke.getMethod(), context);
if (holder == null) {
if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
// For virtual targets we should not report anything as any virtual dispatch just remains.
return DesugarDescription.nothing();
}
// For static, private and special invokes, they may require desugaring and should warn.
return DesugarDescription.builder()
.addScanEffect(
() -> {
if (invoke.isInvokeStatic()) {
leavingStaticInvokeToInterface(context);
}
warnMissingType(context, invoke.getMethod().getHolderType());
})
.build();
}
// Continue with invoke type logic.
if (invoke.isInvokeStatic()) {
return computeInvokeStatic(holder, invoke, context);
}
if (invoke.isInvokeSpecial()) {
return computeInvokeSpecial(holder, invoke, context);
}
if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
return computeInvokeVirtualDispatch(holder, invoke, context);
}
return DesugarDescription.nothing();
}
private DesugarDescription computeNonInterfaceInvoke(CfInvoke invoke, ProgramMethod context) {
assert !invoke.isInterface();
// Emulated interface desugaring will rewrite non-interface invokes.
if (invoke.isInvokeSpecial()) {
DexClass clazz = appView.definitionForHolder(invoke.getMethod(), context);
if (clazz == null) {
return DesugarDescription.nothing();
}
return computeEmulatedInterfaceInvokeSpecial(clazz, invoke.getMethod(), context);
}
if (!invoke.isInvokeVirtual() && !invoke.isInvokeInterface()) {
return DesugarDescription.nothing();
}
DesugarDescription description = computeEmulatedInterfaceVirtualDispatchOrNull(invoke);
if (description != null) {
return description;
}
// It may be the case that a virtual invoke resolves to a static method. In such a case, if
// a default method could give rise to a forwarding method in the resolution path, the program
// would change behavior from throwing ICCE to dispatching to the companion class method.
AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
MethodResolutionResult resolution =
appInfo.resolveMethod(invoke.getMethod(), invoke.isInterface());
if (!resolution.isSingleResolution()
|| !resolution.asSingleResolution().getResolvedMethod().isStatic()) {
return DesugarDescription.nothing();
}
DexClass holder = appInfo.definitionFor(invoke.getMethod().getHolderType(), context);
DexClassAndMethod target = appInfo.lookupMaximallySpecificMethod(holder, invoke.getMethod());
if (target != null && target.isDefaultMethod()) {
// Rewrite the invoke to a throw ICCE as the default method forward would otherwise hide the
// static / virtual mismatch.
return computeInvokeAsThrowRewrite(invoke, resolution.asSingleResolution(), context);
}
return DesugarDescription.nothing();
}
private DesugarDescription computeInvokeSpecial(
DexClass holder, CfInvoke invoke, ProgramMethod context) {
if (invoke.isInvokeSuper(context.getHolderType())) {
return rewriteInvokeSuper(invoke, context);
}
return computeInvokeDirect(holder, invoke, context);
}
private DesugarDescription computeInvokeStatic(
DexClass holder, CfInvoke invoke, ProgramMethod context) {
if (!holder.isInterface()) {
return DesugarDescription.builder()
.addScanEffect(() -> leavingStaticInvokeToInterface(context))
.build();
}
// TODO(b/199135051): This should not be needed. Targeted synthetics should be in place.
if (appView.getSyntheticItems().isPendingSynthetic(invoke.getMethod().getHolderType())) {
// We did not create this code yet, but it will not require rewriting.
return DesugarDescription.nothing();
}
if (isNonDesugaredLibraryClass(holder)) {
// NOTE: we intentionally don't desugar static calls into static interface
// methods coming from android.jar since it is only possible in case v24+
// version of android.jar is provided.
//
// We assume such calls are properly guarded by if-checks like
// 'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
//
// WARNING: This may result in incorrect code on older platforms!
// Retarget call to an appropriate method of companion class.
if (options.canLeaveStaticInterfaceMethodInvokes()) {
// When leaving static interface method invokes upgrade the class file version.
return DesugarDescription.builder()
.addScanEffect(() -> leavingStaticInvokeToInterface(context))
.build();
}
// On pre-L devices static calls to interface methods result in verifier
// rejecting the whole class. We have to create special dispatch classes,
// so the user class is not rejected because it make this call directly.
// TODO(b/166247515): If this an incorrect invoke-static without the interface bit
// we end up "fixing" the code and remove and ICCE error.
if (synthesizedMethods.contains(context)) {
// When reprocessing the method generated below, the desugaring asserts this method
// does not need any new desugaring, while the interface method rewriter tries
// to outline again the invoke-static. Just do nothing instead.
return DesugarDescription.nothing();
}
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context1,
methodProcessingContext,
dexItemFactory) -> {
ProgramMethod newProgramMethod =
appView
.getSyntheticItems()
.createMethod(
kind -> kind.STATIC_INTERFACE_CALL,
methodProcessingContext.createUniqueContext(),
appView,
syntheticMethodBuilder ->
syntheticMethodBuilder
.setProto(invoke.getMethod().getProto())
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(
m ->
ForwardMethodBuilder.builder(factory)
.setStaticTarget(invoke.getMethod(), true)
.setStaticSource(m)
.build()));
synthesizedMethods.add(newProgramMethod);
eventConsumer.acceptInvokeStaticInterfaceOutliningMethod(
newProgramMethod, context1);
// The synthetic dispatch class has static interface method invokes, so set
// the class file version accordingly.
leavingStaticInvokeToInterface(newProgramMethod);
return getInvokeStaticInstructions(newProgramMethod.getReference());
})
.build();
}
SingleResolutionResult resolutionResult =
appView
.appInfoForDesugaring()
.resolveMethodOnInterface(holder, invoke.getMethod())
.asSingleResolution();
if (holder.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, true)) {
return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
}
assert resolutionResult != null;
assert resolutionResult.getResolvedMethod().isStatic();
DexClassAndMethod method = resolutionResult.getResolutionPair();
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context12,
methodProcessingContext,
dexItemFactory) -> {
DexClassAndMethod companionMethod =
helper.ensureStaticAsMethodOfCompanionClassStub(method, eventConsumer);
return getInvokeStaticInstructions(companionMethod.getReference());
})
.build();
}
private DesugarDescription computeInvokeVirtualDispatch(
DexClass holder, CfInvoke invoke, ProgramMethod context) {
AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
SingleResolutionResult resolution =
appInfoForDesugaring
.resolveMethod(invoke.getMethod(), invoke.isInterface())
.asSingleResolution();
if (resolution != null
&& resolution.getResolvedMethod().isPrivate()
&& resolution.isAccessibleFrom(context, appInfoForDesugaring).isTrue()) {
// TODO(b/198267586): What about the private in-accessible case?
return computeInvokeDirect(holder, invoke, context);
}
if (resolution != null && resolution.getResolvedMethod().isStatic()) {
return computeInvokeAsThrowRewrite(invoke, resolution, context);
}
DesugarDescription description = computeEmulatedInterfaceVirtualDispatchOrNull(invoke);
return description != null ? description : DesugarDescription.nothing();
}
private DesugarDescription computeEmulatedInterfaceVirtualDispatchOrNull(CfInvoke invoke) {
MethodResolutionResult resolutionResult =
appView.appInfoForDesugaring().resolveMethod(invoke.getMethod(), invoke.isInterface());
DerivedMethod emulatedDispatchMethod =
helper.computeEmulatedInterfaceDispatchMethod(resolutionResult);
if (emulatedDispatchMethod == null) {
return null;
}
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context1,
methodProcessingContext,
dexItemFactory) ->
getInvokeStaticInstructions(
helper
.ensureEmulatedInterfaceDispatchMethod(
emulatedDispatchMethod, eventConsumer)
.getReference()))
.build();
}
private DesugarDescription computeInvokeDirect(
DexClass clazz, CfInvoke invoke, ProgramMethod context) {
DexMethod invokedMethod = invoke.getMethod();
if (!clazz.isInterface()) {
return DesugarDescription.nothing();
}
if (clazz.isLibraryClass()) {
throw new CompilationError(
"Unexpected call to a private method "
+ "defined in library class "
+ clazz.toSourceString(),
getMethodOrigin(context.getReference()));
}
MethodResolutionResult resolution =
appView.appInfoForDesugaring().resolveMethod(invokedMethod, invoke.isInterface());
if (resolution.isFailedResolution()) {
return computeInvokeAsThrowRewrite(invoke, null, context);
}
SingleResolutionResult singleResolution = resolution.asSingleResolution();
if (singleResolution == null) {
return DesugarDescription.nothing();
}
DexClassAndMethod directTarget = clazz.lookupClassMethod(invokedMethod);
if (directTarget != null) {
// TODO(b/199135051): Replace this by use of the resolution result.
assert directTarget.getDefinition() == singleResolution.getResolutionPair().getDefinition();
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context1,
methodProcessingContext,
dexItemFactory) -> {
// This can be a private instance method call. Note that the referenced
// method is expected to be in the current class since it is private, but desugaring
// may move some methods or their code into other classes.
DexClassAndMethod companionMethodDefinition = null;
DexMethod companionMethod;
if (directTarget.getDefinition().isPrivateMethod()) {
if (directTarget.isProgramMethod()) {
companionMethodDefinition =
helper.ensurePrivateAsMethodOfProgramCompanionClassStub(
directTarget.asProgramMethod());
companionMethod = companionMethodDefinition.getReference();
} else {
// TODO(b/200938617): Why does this not create a stub on the class path?
companionMethod = helper.privateAsMethodOfCompanionClass(directTarget);
}
} else {
companionMethodDefinition =
helper.ensureDefaultAsMethodOfCompanionClassStub(directTarget);
companionMethod = companionMethodDefinition.getReference();
}
if (companionMethodDefinition != null) {
acceptCompanionMethod(directTarget, companionMethodDefinition, eventConsumer);
}
return getInvokeStaticInstructions(companionMethod);
})
.build();
} else {
// The method can be a default method in the interface hierarchy.
DexClassAndMethod virtualTarget =
appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, invokedMethod);
if (virtualTarget != null) {
// TODO(b/199135051): Replace this by use of the resolution result.
assert virtualTarget.getDefinition()
== singleResolution.getResolutionPair().getDefinition();
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context12,
methodProcessingContext,
dexItemFactory) -> {
// This is a invoke-direct call to a virtual method.
DexClassAndMethod companionMethod =
helper.ensureDefaultAsMethodOfCompanionClassStub(virtualTarget);
acceptCompanionMethod(virtualTarget, companionMethod, eventConsumer);
return getInvokeStaticInstructions(companionMethod.getReference());
})
.build();
} else {
// The below assert is here because a well-type program should have a target, but we
// cannot throw a compilation error, since we have no knowledge about the input.
assert false;
}
}
return DesugarDescription.nothing();
}
private DesugarDescription computeInvokeAsThrowRewrite(
CfInvoke invoke, SingleResolutionResult resolution, ProgramMethod context) {
assert !isAlreadyDesugared(invoke, context);
return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowRewrite(
appView, invoke, resolution);
}
private Collection<CfInstruction> getInvokeStaticInstructions(DexMethod newTarget) {
return Collections.singletonList(
new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, newTarget, false));
}
private void leavingStaticInvokeToInterface(ProgramMethod method) {
// When leaving static interface method invokes possibly upgrade the class file
// version, but don't go above the initial class file version. If the input was
// 1.7 or below, this will make a VerificationError on the input a VerificationError
// on the output. If the input was 1.8 or above the runtime behaviour (potential ICCE)
// will remain the same.
if (method.getHolder().hasClassFileVersion()) {
method
.getDefinition()
.upgradeClassFileVersion(
Ordered.min(CfVersion.V1_8, method.getHolder().getInitialClassFileVersion()));
} else {
method.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
}
}
private boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
return appView.getSyntheticItems().isSyntheticMethodThatShouldNotBeDoubleProcessed(method);
}
private void reportInterfaceMethodHandleCallSite(DexCallSite callSite, ProgramMethod context) {
// Check that static interface methods are not referenced from invoke-custom instructions via
// method handles.
reportStaticInterfaceMethodHandle(context, callSite.bootstrapMethod);
for (DexValue arg : callSite.bootstrapArgs) {
if (arg.isDexValueMethodHandle()) {
reportStaticInterfaceMethodHandle(context, arg.asDexValueMethodHandle().value);
}
}
}
private void acceptCompanionMethod(
DexClassAndMethod method,
DexClassAndMethod companion,
InterfaceMethodDesugaringEventConsumer eventConsumer) {
assert method.isProgramMethod() == companion.isProgramMethod();
if (method.isProgramMethod()) {
eventConsumer.acceptCompanionMethod(method.asProgramMethod(), companion.asProgramMethod());
}
}
private DesugarDescription rewriteInvokeSuper(CfInvoke invoke, ProgramMethod context) {
DexMethod invokedMethod = invoke.getMethod();
DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
if (clazz == null) {
// NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
// exception but we can not report it as error since it can also be the intended
// behavior.
return DesugarDescription.builder()
.addScanEffect(() -> warnMissingType(context, invokedMethod.holder))
.build();
}
SingleResolutionResult resolutionResult =
appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, false)) {
return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
}
if (clazz.isInterface() && !clazz.isLibraryClass()) {
// NOTE: we intentionally don't desugar super calls into interface methods
// coming from android.jar since it is only possible in case v24+ version
// of android.jar is provided.
//
// We assume such calls are properly guarded by if-checks like
// 'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
//
// WARNING: This may result in incorrect code on older platforms!
// Retarget call to an appropriate method of companion class.
if (resolutionResult.getResolvedMethod().isPrivateMethod()) {
if (resolutionResult.isAccessibleFrom(context, appView.appInfoForDesugaring()).isFalse()) {
// TODO(b/145775365): This should throw IAE.
return computeInvokeAsThrowRewrite(invoke, null, context);
}
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context1,
methodProcessingContext,
dexItemFactory) -> {
DexClassAndMethod method = resolutionResult.getResolutionPair();
DexMethod companionMethod;
if (method.isProgramMethod()) {
ProgramMethod companionMethodDefinition =
helper.ensurePrivateAsMethodOfProgramCompanionClassStub(
method.asProgramMethod());
companionMethod = companionMethodDefinition.getReference();
eventConsumer.acceptCompanionMethod(
method.asProgramMethod(), companionMethodDefinition);
} else {
companionMethod = helper.privateAsMethodOfCompanionClass(method);
}
return getInvokeStaticInstructions(companionMethod);
})
.build();
} else {
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context12,
methodProcessingContext,
dexItemFactory) -> {
DexClassAndMethod method = resolutionResult.getResolutionPair();
// TODO(b/199135051): Why do this amend routine. We have done resolution, so would
// that not be the correct target!? I think this is just legacy from before
// resolution was implemented in full.
DexMethod amendedMethod =
amendDefaultMethod(context12.getHolder(), invokedMethod);
// TODO(b/234711664): The assertion below is disabled on this release branch.
assert true || method.getReference() == amendedMethod;
DexClassAndMethod companionMethod =
helper.ensureDefaultAsMethodOfCompanionClassStub(method);
acceptCompanionMethod(method, companionMethod, eventConsumer);
return getInvokeStaticInstructions(companionMethod.getReference());
})
.build();
}
}
return computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
}
private DesugarDescription computeEmulatedInterfaceInvokeSpecial(
DexClass clazz, DexMethod invokedMethod, ProgramMethod context) {
DexClassAndMethod superTarget =
appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
if (clazz.isInterface()
&& clazz.isLibraryClass()
&& helper.isInDesugaredLibrary(clazz)
&& !helper.isEmulatedInterface(clazz.type)) {
if (superTarget != null && superTarget.getDefinition().isDefaultMethod()) {
DexClass holder = superTarget.getHolder();
if (holder.isLibraryClass() && holder.isInterface()) {
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context13,
methodProcessingContext,
dexItemFactory) -> {
DexClassAndMethod companionTarget =
helper.ensureDefaultAsMethodOfCompanionClassStub(superTarget);
acceptCompanionMethod(superTarget, companionTarget, eventConsumer);
return getInvokeStaticInstructions(companionTarget.getReference());
})
.build();
}
}
}
// That invoke super may not resolve since the super method may not be present
// since it's in the emulated interface. We need to force resolution. If it resolves
// to a library method, then it needs to be rewritten.
// If it resolves to a program overrides, the invoke-super can remain.
DerivedMethod forwardingMethod =
helper.computeEmulatedInterfaceForwardingMethod(clazz, superTarget);
if (forwardingMethod == null) {
return DesugarDescription.nothing();
}
return DesugarDescription.builder()
.setDesugarRewrite(
(freshLocalProvider,
localStackAllocator,
eventConsumer,
context14,
methodProcessingContext,
dexItemFactory) ->
getInvokeStaticInstructions(
helper.ensureEmulatedInterfaceForwardingMethod(forwardingMethod)))
.build();
}
private boolean shouldRewriteToInvokeToThrow(
SingleResolutionResult resolutionResult, boolean isInvokeStatic) {
return resolutionResult == null
|| resolutionResult.getResolvedMethod().isStatic() != isInvokeStatic;
}
private boolean isNonDesugaredLibraryClass(DexClass clazz) {
return clazz.isLibraryClass() && !helper.isInDesugaredLibrary(clazz);
}
private void reportStaticInterfaceMethodHandle(ProgramMethod context, DexMethodHandle handle) {
if (handle.type.isInvokeStatic()) {
DexClass holderClass = appView.definitionFor(handle.asMethod().holder);
// NOTE: If the class definition is missing we can't check. Let it be handled as any other
// missing call target.
if (holderClass == null) {
warnMissingType(context, handle.asMethod().holder);
} else if (holderClass.isInterface()) {
throw new Unimplemented(
"Desugaring of static interface method handle in `"
+ context.toSourceString()
+ "` is not yet supported.");
}
}
}
// It is possible that referenced method actually points to an interface which does
// not define this default methods, but inherits it. We are making our best effort
// to find an appropriate method, but still use the original one in case we fail.
private DexMethod amendDefaultMethod(DexClass classToDesugar, DexMethod method) {
DexMethod singleCandidate =
getOrCreateInterfaceInfo(classToDesugar, classToDesugar, method.holder)
.getSingleCandidate(method);
return singleCandidate != null ? singleCandidate : method;
}
public InterfaceMethodProcessorFacade getPostProcessingDesugaringD8(
Flavor flavour, InterfaceProcessor interfaceProcessor) {
return new InterfaceMethodProcessorFacade(appView, flavour, m -> true, interfaceProcessor);
}
public InterfaceMethodProcessorFacade getPostProcessingDesugaringR8(
Flavor flavour,
Predicate<ProgramMethod> isLiveMethod,
InterfaceProcessor interfaceProcessor) {
return new InterfaceMethodProcessorFacade(appView, flavour, isLiveMethod, interfaceProcessor);
}
private Origin getMethodOrigin(DexMethod method) {
DexType holder = method.holder;
if (InterfaceDesugaringSyntheticHelper.isCompanionClassType(holder)) {
holder = helper.getInterfaceClassType(holder);
}
DexClass clazz = appView.definitionFor(holder);
return clazz == null ? Origin.unknown() : clazz.getOrigin();
}
final DefaultMethodsHelper.Collection getOrCreateInterfaceInfo(
DexClass classToDesugar, DexClass implementing, DexType iface) {
DefaultMethodsHelper.Collection collection = cache.get(iface);
if (collection != null) {
return collection;
}
collection = createInterfaceInfo(classToDesugar, implementing, iface);
DefaultMethodsHelper.Collection existing = cache.putIfAbsent(iface, collection);
return existing != null ? existing : collection;
}
private DefaultMethodsHelper.Collection createInterfaceInfo(
DexClass classToDesugar, DexClass implementing, DexType iface) {
DefaultMethodsHelper helper = new DefaultMethodsHelper();
DexClass definedInterface = appView.definitionFor(iface);
if (definedInterface == null) {
this.helper.warnMissingInterface(classToDesugar, implementing, iface);
return helper.wrapInCollection();
}
if (!definedInterface.isInterface()) {
throw new CompilationError(
"Type "
+ iface.toSourceString()
+ " is referenced as an interface from `"
+ implementing.toString()
+ "`.");
}
if (isNonDesugaredLibraryClass(definedInterface)) {
// NOTE: We intentionally ignore all candidates coming from android.jar
// since it is only possible in case v24+ version of android.jar is provided.
// WARNING: This may result in incorrect code if something else than Android bootclasspath
// classes are given as libraries!
return helper.wrapInCollection();
}
// At this point we likely have a non-library type that may depend on default method information
// from its interfaces and the dependency should be reported.
if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) {
reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.appInfo());
}
// Merge information from all superinterfaces.
for (DexType superinterface : definedInterface.interfaces.values) {
helper.merge(getOrCreateInterfaceInfo(classToDesugar, definedInterface, superinterface));
}
// Hide by virtual methods of this interface.
for (DexEncodedMethod virtual : definedInterface.virtualMethods()) {
helper.hideMatches(virtual.getReference());
}
// Add all default methods of this interface.
for (DexEncodedMethod encoded : definedInterface.virtualMethods()) {
if (this.helper.isCompatibleDefaultMethod(encoded)) {
helper.addDefaultMethod(encoded);
}
}
return helper.wrapInCollection();
}
private void warnMissingType(ProgramMethod context, DexType missing) {
// Companion/Emulated interface/Conversion classes for desugared library won't be missing,
// they are in the desugared library.
if (helper.shouldIgnoreFromReports(missing)) {
return;
}
DexMethod method = appView.graphLens().getOriginalMethodSignature(context.getReference());
Origin origin = getMethodOrigin(method);
MethodPosition position = new MethodPosition(method.asMethodReference());
options.warningMissingTypeForDesugar(origin, position, missing, method);
}
public static void reportDependencyEdge(
DexClass dependent, DexClass dependency, AppInfo appInfo) {
assert !dependent.isLibraryClass();
assert !dependency.isLibraryClass();
DesugarGraphConsumer consumer = appInfo.app().options.desugarGraphConsumer;
if (consumer != null) {
Origin dependencyOrigin = dependency.getOrigin();
java.util.Collection<DexType> dependents =
appInfo.getSyntheticItems().getSynthesizingContextTypes(dependent.getType());
if (dependents.isEmpty()) {
reportDependencyEdge(consumer, dependencyOrigin, dependent);
} else {
for (DexType type : dependents) {
reportDependencyEdge(consumer, dependencyOrigin, appInfo.definitionFor(type));
}
}
}
}
private static void reportDependencyEdge(
DesugarGraphConsumer consumer, Origin dependencyOrigin, DexClass clazz) {
Origin dependentOrigin = clazz.getOrigin();
if (dependentOrigin != dependencyOrigin) {
consumer.accept(dependentOrigin, dependencyOrigin);
}
}
}