blob: ea1d1021b4a1f1752f236e4330270f3daececc3e [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 static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
import com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.cf.CfVersion;
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.AppView;
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexApplication.Builder;
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.DexProgramClass;
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.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeSuper;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.ir.desugar.itf.DefaultMethodsHelper.Collection;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
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.synthesis.SyntheticNaming;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
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 {
// Public for testing.
public static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
public static final String COMPANION_CLASS_NAME_SUFFIX = "$-CC";
public static final String DEFAULT_METHOD_PREFIX = "$default$";
public static final String PRIVATE_METHOD_PREFIX = "$private$";
private final AppView<?> appView;
private final IRConverter converter;
private final InternalOptions options;
final DexItemFactory factory;
private final Map<DexType, DexType> emulatedInterfaces;
// 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<>();
private final Predicate<DexType> shouldIgnoreFromReportsPredicate;
/** 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, IRConverter converter) {
assert converter != null;
this.appView = appView;
this.converter = converter;
this.options = appView.options();
this.factory = appView.dexItemFactory();
this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
initializeEmulatedInterfaceVariables();
}
public static void checkForAssumedLibraryTypes(AppInfo appInfo, InternalOptions options) {
DesugaredLibraryConfiguration config = options.desugaredLibraryConfiguration;
BiConsumer<DexType, DexType> registerEntry = registerMapEntry(appInfo);
config.getEmulateLibraryInterface().forEach(registerEntry);
config.getCustomConversions().forEach(registerEntry);
config.getRetargetCoreLibMember().forEach((method, types) -> types.forEach(registerEntry));
}
private static BiConsumer<DexType, DexType> registerMapEntry(AppInfo appInfo) {
return (key, value) -> {
registerType(appInfo, key);
registerType(appInfo, value);
};
}
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);
}
}
});
}
}
private void initializeEmulatedInterfaceVariables() {
Map<DexType, DexType> emulateLibraryInterface =
options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
for (DexType interfaceType : emulateLibraryInterface.keySet()) {
addRewriteRulesForEmulatedInterface(
interfaceType, emulateLibraryInterface.get(interfaceType).toSourceString());
DexClass emulatedInterfaceClass = appView.definitionFor(interfaceType);
if (emulatedInterfaceClass != null) {
for (DexEncodedMethod encodedMethod :
emulatedInterfaceClass.methods(DexEncodedMethod::isDefaultMethod)) {
emulatedMethods.add(encodedMethod.getReference().name);
}
}
}
}
void addRewriteRulesForEmulatedInterface(
DexType emulatedInterface, String rewrittenEmulatedInterface) {
addCompanionClassRewriteRule(emulatedInterface, rewrittenEmulatedInterface);
appView.rewritePrefix.rewriteType(
getEmulateLibraryInterfaceClassType(emulatedInterface, factory),
factory.createType(
DescriptorUtils.javaTypeToDescriptor(
rewrittenEmulatedInterface + EMULATE_LIBRARY_CLASS_NAME_SUFFIX)));
}
void addCompanionClassRewriteRule(DexType interfaceType, String rewrittenType) {
appView.rewritePrefix.rewriteType(
getCompanionClassType(interfaceType),
factory.createType(
DescriptorUtils.javaTypeToDescriptor(rewrittenType + COMPANION_CLASS_NAME_SUFFIX)));
}
boolean isEmulatedInterface(DexType itf) {
return emulatedInterfaces.containsKey(itf);
}
public boolean needsRewriting(DexMethod method, Type invokeType) {
if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
if (clazz != null && clazz.isInterface()) {
return true;
}
}
return emulatedMethods.contains(method.getName());
}
DexType getEmulatedInterface(DexType itf) {
return emulatedInterfaces.get(itf);
}
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);
}
}
// Rewrites the references to static and default interface methods.
// NOTE: can be called for different methods concurrently.
public void rewriteMethodReferences(
IRCode code,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
ProgramMethod context = code.context();
if (synthesizedMethods.contains(context)) {
return;
}
Set<Value> affectedValues = Sets.newIdentityHashSet();
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
if (blocksToRemove.contains(block)) {
continue;
}
InstructionListIterator instructions = block.listIterator(code);
while (instructions.hasNext()) {
Instruction instruction = instructions.next();
switch (instruction.opcode()) {
case INVOKE_CUSTOM:
rewriteInvokeCustom(instruction.asInvokeCustom(), context);
break;
case INVOKE_DIRECT:
rewriteInvokeDirect(instruction.asInvokeDirect(), instructions, context);
break;
case INVOKE_STATIC:
rewriteInvokeStatic(
instruction.asInvokeStatic(),
code,
blocks,
instructions,
affectedValues,
blocksToRemove,
methodProcessor,
methodProcessingContext);
break;
case INVOKE_SUPER:
rewriteInvokeSuper(
instruction.asInvokeSuper(),
code,
blocks,
instructions,
affectedValues,
blocksToRemove,
methodProcessor,
methodProcessingContext);
break;
case INVOKE_INTERFACE:
case INVOKE_VIRTUAL:
rewriteInvokeInterfaceOrInvokeVirtual(
instruction.asInvokeMethodWithReceiver(), instructions);
break;
default:
// Intentionally empty.
break;
}
}
}
code.removeBlocks(blocksToRemove);
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
assert code.isConsistentSSA();
}
private void rewriteInvokeCustom(InvokeCustom invoke, ProgramMethod context) {
// Check that static interface methods are not referenced from invoke-custom instructions via
// method handles.
DexCallSite callSite = invoke.getCallSite();
reportStaticInterfaceMethodHandle(context, callSite.bootstrapMethod);
for (DexValue arg : callSite.bootstrapArgs) {
if (arg.isDexValueMethodHandle()) {
reportStaticInterfaceMethodHandle(context, arg.asDexValueMethodHandle().value);
}
}
}
private void rewriteInvokeDirect(
InvokeDirect invoke, InstructionListIterator instructions, ProgramMethod context) {
DexMethod method = invoke.getInvokedMethod();
if (factory.isConstructor(method)) {
return;
}
DexClass clazz = appView.definitionForHolder(method, context);
if (clazz == null) {
// Report missing class since we don't know if it is an interface.
warnMissingType(context, method.holder);
return;
}
if (!clazz.isInterface()) {
return;
}
if (clazz.isLibraryClass()) {
throw new CompilationError(
"Unexpected call to a private method "
+ "defined in library class "
+ clazz.toSourceString(),
getMethodOrigin(context.getReference()));
}
DexClassAndMethod directTarget = clazz.lookupClassMethod(method);
if (directTarget != null) {
// 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.
assert needsRewriting(method, DIRECT);
instructions.replaceCurrentInstruction(
new InvokeStatic(
directTarget.getDefinition().isPrivateMethod()
? privateAsMethodOfCompanionClass(directTarget)
: defaultAsMethodOfCompanionClass(directTarget),
invoke.outValue(),
invoke.arguments()));
} else {
// The method can be a default method in the interface hierarchy.
DexClassAndMethod virtualTarget =
appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
if (virtualTarget != null) {
// This is a invoke-direct call to a virtual method.
assert needsRewriting(method, DIRECT);
instructions.replaceCurrentInstruction(
new InvokeStatic(
defaultAsMethodOfCompanionClass(virtualTarget),
invoke.outValue(),
invoke.arguments()));
} 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;
}
}
}
private void rewriteInvokeStatic(
InvokeStatic invoke,
IRCode code,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructions,
Set<Value> affectedValues,
Set<BasicBlock> blocksToRemove,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
DexMethod invokedMethod = invoke.getInvokedMethod();
if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
// We did not create this code yet, but it will not require rewriting.
return;
}
ProgramMethod context = code.context();
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.
if (invoke.getInterfaceBit()) {
leavingStaticInvokeToInterface(context);
}
warnMissingType(context, invokedMethod.holder);
return;
}
if (!clazz.isInterface()) {
if (invoke.getInterfaceBit()) {
leavingStaticInvokeToInterface(context);
}
return;
}
if (isNonDesugaredLibraryClass(clazz)) {
// 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()) {
// 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.
ProgramMethod newProgramMethod =
appView
.getSyntheticItems()
.createMethod(
SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
methodProcessingContext.createUniqueContext(),
appView,
syntheticMethodBuilder ->
syntheticMethodBuilder
.setProto(invokedMethod.proto)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(
m ->
ForwardMethodBuilder.builder(factory)
.setStaticTarget(invokedMethod, true)
.setStaticSource(m)
.build()));
assert needsRewriting(invokedMethod, STATIC);
instructions.replaceCurrentInstruction(
new InvokeStatic(
newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
synchronized (synthesizedMethods) {
// The synthetic dispatch class has static interface method invokes, so set
// the class file version accordingly.
newProgramMethod.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
synthesizedMethods.add(newProgramMethod);
}
} else {
// When leaving static interface method invokes upgrade the class file version.
context.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
}
return;
}
SingleResolutionResult resolutionResult =
appView
.appInfoForDesugaring()
.resolveMethodOnInterface(clazz, invokedMethod)
.asSingleResolution();
if (clazz.isInterface()
&& rewriteInvokeToThrow(
invoke,
resolutionResult,
code,
blockIterator,
instructions,
affectedValues,
blocksToRemove,
methodProcessor,
methodProcessingContext)) {
assert needsRewriting(invoke.getInvokedMethod(), STATIC);
return;
}
assert resolutionResult != null;
assert resolutionResult.getResolvedMethod().isStatic();
assert needsRewriting(invokedMethod, STATIC);
instructions.replaceCurrentInstruction(
new InvokeStatic(
staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
invoke.outValue(),
invoke.arguments()));
}
private void rewriteInvokeSuper(
InvokeSuper invoke,
IRCode code,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructions,
Set<Value> affectedValues,
Set<BasicBlock> blocksToRemove,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
ProgramMethod context = code.context();
DexMethod invokedMethod = invoke.getInvokedMethod();
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.
warnMissingType(context, invokedMethod.holder);
return;
}
SingleResolutionResult resolutionResult =
appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
if (clazz.isInterface()
&& rewriteInvokeToThrow(
invoke,
resolutionResult,
code,
blockIterator,
instructions,
affectedValues,
blocksToRemove,
methodProcessor,
methodProcessingContext)) {
assert needsRewriting(invoke.getInvokedMethod(), SUPER);
return;
}
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.
assert needsRewriting(invokedMethod, SUPER);
DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
instructions.replaceCurrentInstruction(
new InvokeStatic(
defaultAsMethodOfCompanionClass(amendedMethod, appView.dexItemFactory()),
invoke.outValue(),
invoke.arguments()));
} else {
DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
if (emulatedItf == null) {
if (clazz.isInterface() && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
DexClassAndMethod target =
appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
if (target != null && target.getDefinition().isDefaultMethod()) {
DexClass holder = target.getHolder();
if (holder.isLibraryClass() && holder.isInterface()) {
assert needsRewriting(invokedMethod, SUPER);
instructions.replaceCurrentInstruction(
new InvokeStatic(
defaultAsMethodOfCompanionClass(target),
invoke.outValue(),
invoke.arguments()));
}
}
}
} else {
// 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.
DexClassAndMethod superTarget =
appView.appInfoForDesugaring().lookupSuperTarget(invoke.getInvokedMethod(), context);
if (superTarget != null && superTarget.isLibraryMethod()) {
// Rewriting is required because the super invoke resolves into a missing
// method (method is on desugared library). Find out if it needs to be
// retarget or if it just calls a companion class method and rewrite.
DexMethod retargetMethod =
options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
if (retargetMethod == null) {
DexMethod originalCompanionMethod = defaultAsMethodOfCompanionClass(superTarget);
DexMethod companionMethod =
factory.createMethod(
getCompanionClassType(emulatedItf),
factory.protoWithDifferentFirstParameter(
originalCompanionMethod.proto, emulatedItf),
originalCompanionMethod.name);
assert needsRewriting(invokedMethod, SUPER);
instructions.replaceCurrentInstruction(
new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
} else {
assert needsRewriting(invokedMethod, SUPER);
instructions.replaceCurrentInstruction(
new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
}
}
}
}
}
private void rewriteInvokeInterfaceOrInvokeVirtual(
InvokeMethodWithReceiver invoke, InstructionListIterator instructions) {
DexMethod invokedMethod = invoke.getInvokedMethod();
DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
if (emulatedItf == null) {
return;
}
// The call potentially ends up in a library class, in which case we need to rewrite, since the
// code may be in the desugared library.
SingleResolutionResult resolution =
appView
.appInfoForDesugaring()
.resolveMethod(invokedMethod, invoke.getInterfaceBit())
.asSingleResolution();
if (resolution != null
&& (resolution.getResolvedHolder().isLibraryClass()
|| appView.options().isDesugaredLibraryCompilation())) {
assert needsRewriting(invokedMethod, VIRTUAL);
rewriteCurrentInstructionToEmulatedInterfaceCall(
emulatedItf, invokedMethod, invoke, instructions);
}
}
private boolean rewriteInvokeToThrow(
InvokeMethod invoke,
SingleResolutionResult resolutionResult,
IRCode code,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructions,
Set<Value> affectedValues,
Set<BasicBlock> blocksToRemove,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
MethodSynthesizerConsumer methodSynthesizerConsumer;
if (resolutionResult == null) {
methodSynthesizerConsumer =
UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
} else if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
methodSynthesizerConsumer =
UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
} else {
return false;
}
// Replace by throw new SomeException.
UtilityMethodForCodeOptimizations throwMethod =
methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
throwMethod.optimize(methodProcessor);
InvokeStatic throwInvoke =
InvokeStatic.builder()
.setMethod(throwMethod.getMethod())
.setFreshOutValue(appView, code)
.setPosition(invoke)
.build();
instructions.previous();
// Split the block before the invoke instruction, and position the block iterator at the newly
// created throw block (this involves rewinding the block iterator back over the blocks created
// as a result of critical edge splitting, if any).
BasicBlock throwBlock = instructions.splitCopyCatchHandlers(code, blockIterator, options);
IteratorUtils.previousUntil(blockIterator, block -> block == throwBlock);
blockIterator.next();
// Insert the `SomeException e = throwSomeException()` invoke before the goto
// instruction.
instructions.previous();
instructions.add(throwInvoke);
// Insert the `throw e` instruction in the newly created throw block.
InstructionListIterator throwBlockIterator = throwBlock.listIterator(code);
throwBlockIterator.next();
throwBlockIterator.replaceCurrentInstructionWithThrow(
appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
return true;
}
private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
// Here we try to avoid doing the expensive look-up on all invokes.
if (!emulatedMethods.contains(invokedMethod.name)) {
return null;
}
DexClass dexClass = appView.definitionFor(invokedMethod.holder);
// We cannot rewrite the invoke we do not know what the class is.
if (dexClass == null) {
return null;
}
DexEncodedMethod singleTarget = null;
if (dexClass.isInterface()) {
// Look for exact method on the interface.
singleTarget = dexClass.lookupMethod(invokedMethod);
}
if (singleTarget == null) {
DexClassAndMethod result =
appView.appInfoForDesugaring().lookupMaximallySpecificMethod(dexClass, invokedMethod);
if (result != null) {
singleTarget = result.getDefinition();
}
}
if (singleTarget == null) {
// At this point we are in a library class. Failures can happen with NoSuchMethod if a
// library class implement a method with same signature but not related to emulated
// interfaces.
return null;
}
if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.getHolderType())) {
return singleTarget.getHolderType();
}
return null;
}
private void rewriteCurrentInstructionToEmulatedInterfaceCall(
DexType emulatedItf,
DexMethod invokedMethod,
InvokeMethod invokeMethod,
InstructionListIterator instructions) {
DexClassAndMethod defaultMethod =
appView.definitionFor(emulatedItf).lookupClassMethod(invokedMethod);
if (defaultMethod != null && !dontRewrite(defaultMethod)) {
assert !defaultMethod.getAccessFlags().isAbstract();
instructions.replaceCurrentInstruction(
new InvokeStatic(
emulateInterfaceLibraryMethod(defaultMethod, factory),
invokeMethod.outValue(),
invokeMethod.arguments()));
}
}
private boolean isNonDesugaredLibraryClass(DexClass clazz) {
return clazz.isLibraryClass() && !isInDesugaredLibrary(clazz);
}
boolean isInDesugaredLibrary(DexClass clazz) {
assert clazz.isLibraryClass() || options.isDesugaredLibraryCompilation();
if (emulatedInterfaces.containsKey(clazz.type)) {
return true;
}
return appView.rewritePrefix.hasRewrittenType(clazz.type, appView);
}
boolean dontRewrite(DexClassAndMethod method) {
for (Pair<DexType, DexString> dontRewrite :
options.desugaredLibraryConfiguration.getDontRewriteInvocation()) {
if (method.getHolderType() == dontRewrite.getFirst()
&& method.getName() == dontRewrite.getSecond()) {
return true;
}
}
return false;
}
public static DexMethod emulateInterfaceLibraryMethod(
DexClassAndMethod method, DexItemFactory factory) {
return factory.createMethod(
getEmulateLibraryInterfaceClassType(method.getHolderType(), factory),
factory.prependTypeToProto(method.getHolderType(), method.getProto()),
method.getName());
}
private static String getEmulateLibraryInterfaceClassDescriptor(String descriptor) {
return descriptor.substring(0, descriptor.length() - 1)
+ EMULATE_LIBRARY_CLASS_NAME_SUFFIX
+ ";";
}
public static DexType getEmulateLibraryInterfaceClassType(DexType type, DexItemFactory factory) {
assert type.isClassType();
String descriptor = type.descriptor.toString();
String elTypeDescriptor = getEmulateLibraryInterfaceClassDescriptor(descriptor);
return factory.createSynthesizedType(elTypeDescriptor);
}
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.");
}
}
}
public static String getCompanionClassDescriptor(String descriptor) {
return descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";";
}
// Gets the companion class for the interface `type`.
static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
assert type.isClassType();
String descriptor = type.descriptor.toString();
String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
return factory.createSynthesizedType(ccTypeDescriptor);
}
public DexType getCompanionClassType(DexType type) {
return getCompanionClassType(type, factory);
}
// Checks if `type` is a companion class.
public static boolean isCompanionClassType(DexType type) {
return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
}
public static boolean isEmulatedLibraryClassType(DexType type) {
return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
}
// Gets the interface class for a companion class `type`.
private DexType getInterfaceClassType(DexType type) {
return getInterfaceClassType(type, factory);
}
// Gets the interface class for a companion class `type`.
public static DexType getInterfaceClassType(DexType type, DexItemFactory factory) {
assert isCompanionClassType(type);
String descriptor = type.descriptor.toString();
String interfaceTypeDescriptor =
descriptor.substring(0, descriptor.length() - 1 - COMPANION_CLASS_NAME_SUFFIX.length())
+ ";";
return factory.createType(interfaceTypeDescriptor);
}
// Represent a static interface method as a method of companion class.
final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
recordCompanionClassReference(appView, method, rewritten);
return rewritten;
}
private static DexMethod instanceAsMethodOfCompanionClass(
DexMethod method, String prefix, DexItemFactory factory) {
// Add an implicit argument to represent the receiver.
DexType[] params = method.proto.parameters.values;
DexType[] newParams = new DexType[params.length + 1];
newParams[0] = method.holder;
System.arraycopy(params, 0, newParams, 1, params.length);
// Add prefix to avoid name conflicts.
return factory.createMethod(
getCompanionClassType(method.holder, factory),
factory.createProto(method.proto.returnType, newParams),
factory.createString(prefix + method.name.toString()));
}
// 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;
}
// Represent a default interface method as a method of companion class.
public static DexMethod defaultAsMethodOfCompanionClass(
DexMethod method, DexItemFactory factory) {
return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX, factory);
}
public final DexMethod defaultAsMethodOfCompanionClass(DexClassAndMethod method) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexMethod rewritten = defaultAsMethodOfCompanionClass(method.getReference(), dexItemFactory);
recordCompanionClassReference(appView, method, rewritten);
return rewritten;
}
// Represent a private instance interface method as a method of companion class.
static DexMethod privateAsMethodOfCompanionClass(DexMethod method, DexItemFactory factory) {
// Add an implicit argument to represent the receiver.
return instanceAsMethodOfCompanionClass(method, PRIVATE_METHOD_PREFIX, factory);
}
private DexMethod privateAsMethodOfCompanionClass(DexClassAndMethod method) {
return privateAsMethodOfCompanionClass(method.getReference(), factory);
}
private static void recordCompanionClassReference(
AppView<?> appView, DexClassAndMethod method, DexMethod rewritten) {
ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
// If the interface class is a program class, we shouldn't need to synthesize the companion
// class on the classpath.
if (context == null) {
return;
}
appView
.getSyntheticItems()
.ensureDirectMethodOnSyntheticClasspathClassWhileMigrating(
SyntheticKind.COMPANION_CLASS,
rewritten.getHolderType(),
context,
appView,
rewritten,
builder ->
builder
.setName(rewritten.name)
.setProto(rewritten.proto)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
}
/**
* Move static and default interface methods to companion classes, add missing methods to forward
* to moved default methods implementation.
*/
public void desugarInterfaceMethods(
Builder<?> builder, Flavor flavour, ExecutorService executorService)
throws ExecutionException {
// During L8 compilation, emulated interfaces are processed to be renamed, to have
// their interfaces fixed-up and to generate the emulated dispatch code.
EmulatedInterfaceProcessor emulatedInterfaceProcessor =
new EmulatedInterfaceProcessor(appView, this);
// Process all classes first. Add missing forwarding methods to
// replace desugared default interface methods.
ClassProcessor classProcessor = new ClassProcessor(appView, this);
// Process interfaces, create companion or dispatch class if needed, move static
// methods to companion class, copy default interface methods to companion classes,
// make original default methods abstract, remove bridge methods, create dispatch
// classes if needed.
InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView, this);
// The interface processors must be ordered so that finalization of the processing is performed
// in that order. The emulatedInterfaceProcessor has to be last at this point to avoid renaming
// emulated interfaces before the other processing.
ImmutableList<InterfaceDesugaringProcessor> orderedInterfaceDesugaringProcessors =
ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
processClassesConcurrently(
orderedInterfaceDesugaringProcessors, builder, flavour, executorService);
SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.create();
sortedSynthesizedMethods.addAll(synthesizedMethods);
converter.processMethodsConcurrently(sortedSynthesizedMethods, executorService);
// Cached data is not needed any more.
clear();
}
private void clear() {
this.cache.clear();
this.synthesizedMethods.clear();
}
private boolean shouldProcess(DexProgramClass clazz, Flavor flavour) {
if (appView.isAlreadyLibraryDesugared(clazz)) {
return false;
}
return (!clazz.originatesFromDexResource() || flavour == Flavor.IncludeAllResources);
}
private void processClassesConcurrently(
List<InterfaceDesugaringProcessor> processors,
Builder<?> builder,
Flavor flavour,
ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processItems(
Iterables.filter(
builder.getProgramClasses(), (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
clazz -> {
for (InterfaceDesugaringProcessor processor : processors) {
processor.process(clazz, synthesizedMethods);
}
},
executorService);
for (InterfaceDesugaringProcessor processor : processors) {
processor.finalizeProcessing(builder, synthesizedMethods);
}
}
final boolean isDefaultMethod(DexEncodedMethod method) {
assert !method.accessFlags.isConstructor();
assert !method.accessFlags.isStatic();
if (method.accessFlags.isAbstract()) {
return false;
}
if (method.accessFlags.isNative()) {
throw new Unimplemented("Native default interface methods are not yet supported.");
}
if (!method.accessFlags.isPublic()) {
// NOTE: even though the class is allowed to have non-public interface methods
// with code, for example private methods, all such methods we are aware of are
// created by the compiler for stateful lambdas and they must be converted into
// static methods by lambda desugaring by this time.
throw new Unimplemented("Non public default interface methods are not yet supported.");
}
return true;
}
private Predicate<DexType> getShouldIgnoreFromReportsPredicate(AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
DexString retargetPackageAndClassPrefixDescriptor =
dexItemFactory.createString(
getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
DexString typeWrapperClassNameDescriptorSuffix =
dexItemFactory.createString(TYPE_WRAPPER_SUFFIX + ';');
DexString vivifiedTypeWrapperClassNameDescriptorSuffix =
dexItemFactory.createString(VIVIFIED_TYPE_WRAPPER_SUFFIX + ';');
DexString companionClassNameDescriptorSuffix =
dexItemFactory.createString(COMPANION_CLASS_NAME_SUFFIX + ";");
return type -> {
DexString descriptor = type.getDescriptor();
return appView.rewritePrefix.hasRewrittenType(type, appView)
|| descriptor.endsWith(typeWrapperClassNameDescriptorSuffix)
|| descriptor.endsWith(vivifiedTypeWrapperClassNameDescriptorSuffix)
|| descriptor.endsWith(companionClassNameDescriptorSuffix)
|| emulatedInterfaces.containsValue(type)
|| options.desugaredLibraryConfiguration.getCustomConversions().containsValue(type)
|| appView.getDontWarnConfiguration().matches(type)
|| descriptor.startsWith(retargetPackageAndClassPrefixDescriptor);
};
}
private boolean shouldIgnoreFromReports(DexType missing) {
return shouldIgnoreFromReportsPredicate.test(missing);
}
void warnMissingInterface(DexClass classToDesugar, DexClass implementing, DexType missing) {
// We use contains() on non hashed collection, but we know it's a 8 cases collection.
// j$ interfaces won't be missing, they are in the desugared library.
if (shouldIgnoreFromReports(missing)) {
return;
}
options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
}
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 (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);
}
private Origin getMethodOrigin(DexMethod method) {
DexType holder = method.holder;
if (isCompanionClassType(holder)) {
holder = 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);
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) {
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 (isDefaultMethod(encoded)) {
helper.addDefaultMethod(encoded);
}
}
return helper.wrapInCollection();
}
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);
}
}
}