Split interface method rewriting into invoke-specific rewriting methods
Change-Id: I465d23d8d76512e9608abbfb940543da7dc3b892
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 18d0352..891beb3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -4,6 +4,13 @@
package com.android.tools.r8.ir.desugar;
+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 com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.dex.Constants;
@@ -36,8 +43,10 @@
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.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.conversion.IRConverter;
@@ -241,271 +250,295 @@
}
ListIterator<BasicBlock> blocks = code.listIterator();
- AppInfo appInfo = appView.appInfo();
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
InstructionListIterator instructions = block.listIterator(code);
while (instructions.hasNext()) {
Instruction instruction = instructions.next();
-
- if (instruction.isInvokeCustom()) {
- // Check that static interface methods are not referenced
- // from invoke-custom instructions via method handles.
- DexCallSite callSite = instruction.asInvokeCustom().getCallSite();
- reportStaticInterfaceMethodHandle(context, callSite.bootstrapMethod);
- for (DexValue arg : callSite.bootstrapArgs) {
- if (arg.isDexValueMethodHandle()) {
- reportStaticInterfaceMethodHandle(context, arg.asDexValueMethodHandle().value);
- }
- }
- continue;
+ 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(), instructions, context);
+ break;
+ case INVOKE_SUPER:
+ rewriteInvokeSuper(instruction.asInvokeSuper(), instructions, context);
+ break;
+ case INVOKE_INTERFACE:
+ case INVOKE_VIRTUAL:
+ rewriteInvokeInterfaceOrInvokeVirtual(
+ instruction.asInvokeMethodWithReceiver(), instructions);
+ break;
+ default:
+ // Intentionally empty.
+ break;
}
+ }
+ }
+ }
- if (instruction.isInvokeStatic()) {
- InvokeStatic invokeStatic = instruction.asInvokeStatic();
- DexMethod method = invokeStatic.getInvokedMethod();
- if (appView.getSyntheticItems().isPendingSynthetic(method.holder)) {
- // We did not create this code yet, but it will not require rewriting.
- continue;
- }
- DexClass clazz = appInfo.definitionFor(method.holder);
- 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 (invokeStatic.getInterfaceBit()) {
- leavingStaticInvokeToInterface(context);
- }
- warnMissingType(context, method.holder);
- } else if (clazz.isInterface()) {
- 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.
+ 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);
+ }
+ }
+ }
- 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(
- context.getHolder(),
- factory,
- syntheticMethodBuilder ->
- syntheticMethodBuilder
- .setProto(method.proto)
- .setAccessFlags(
- MethodAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC
- | Constants.ACC_STATIC
- | Constants.ACC_SYNTHETIC,
- false))
- .setCode(
- m ->
- ForwardMethodBuilder.builder(factory)
- .setStaticTarget(method, true)
- .setStaticSource(m)
- .build()));
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- newProgramMethod.getReference(),
- invokeStatic.outValue(),
- invokeStatic.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);
- }
- } else {
+ 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()));
+ }
+
+ DexEncodedMethod directTarget = clazz.lookupMethod(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.
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ directTarget.isPrivateMethod()
+ ? privateAsMethodOfCompanionClass(method)
+ : defaultAsMethodOfCompanionClass(method),
+ 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.
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ defaultAsMethodOfCompanionClass(virtualTarget.getDefinition().method),
+ 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, InstructionListIterator instructions, ProgramMethod context) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
+ // We did not create this code yet, but it will not require rewriting.
+ return;
+ }
+
+ 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(
+ context.getHolder(),
+ factory,
+ syntheticMethodBuilder ->
+ syntheticMethodBuilder
+ .setProto(invokedMethod.proto)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC
+ | Constants.ACC_STATIC
+ | Constants.ACC_SYNTHETIC,
+ false))
+ .setCode(
+ m ->
+ ForwardMethodBuilder.builder(factory)
+ .setStaticTarget(invokedMethod, true)
+ .setStaticSource(m)
+ .build()));
+ 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);
+ }
+ } else {
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ staticAsMethodOfCompanionClass(invokedMethod),
+ invoke.outValue(),
+ invoke.arguments()));
+ }
+ }
+
+ private void rewriteInvokeSuper(
+ InvokeSuper invoke, InstructionListIterator instructions, ProgramMethod 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;
+ }
+
+ 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.
+ DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ defaultAsMethodOfCompanionClass(amendedMethod),
+ 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()) {
instructions.replaceCurrentInstruction(
- new InvokeStatic(staticAsMethodOfCompanionClass(method),
- invokeStatic.outValue(), invokeStatic.arguments()));
- }
- } else {
- assert !clazz.isInterface();
- if (invokeStatic.getInterfaceBit()) {
- leavingStaticInvokeToInterface(context);
+ new InvokeStatic(
+ defaultAsMethodOfCompanionClass(target.getReference(), factory),
+ invoke.outValue(),
+ invoke.arguments()));
}
}
- continue;
}
-
- if (instruction.isInvokeSuper()) {
- InvokeSuper invokeSuper = instruction.asInvokeSuper();
- DexMethod invokedMethod = invokeSuper.getInvokedMethod();
- DexClass clazz = appInfo.definitionFor(invokedMethod.holder);
- 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);
- } else 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.
- DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
+ } 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 =
+ instanceAsMethodOfCompanionClass(
+ superTarget.getReference(), DEFAULT_METHOD_PREFIX, factory);
+ DexMethod companionMethod =
+ factory.createMethod(
+ getCompanionClassType(emulatedItf),
+ factory.protoWithDifferentFirstParameter(
+ originalCompanionMethod.proto, emulatedItf),
+ originalCompanionMethod.name);
instructions.replaceCurrentInstruction(
- new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
- invokeSuper.outValue(), invokeSuper.arguments()));
+ new InvokeStatic(companionMethod, 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, code.context());
- if (target != null && target.getDefinition().isDefaultMethod()) {
- DexClass holder = target.getHolder();
- if (holder.isLibraryClass() && holder.isInterface()) {
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- defaultAsMethodOfCompanionClass(target.getReference(), factory),
- invokeSuper.outValue(),
- invokeSuper.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(invokeSuper.getInvokedMethod(), code.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 =
- instanceAsMethodOfCompanionClass(
- superTarget.getReference(), DEFAULT_METHOD_PREFIX, factory);
- DexMethod companionMethod =
- factory.createMethod(
- getCompanionClassType(emulatedItf),
- factory.protoWithDifferentFirstParameter(
- originalCompanionMethod.proto, emulatedItf),
- originalCompanionMethod.name);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
- } else {
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
- }
- }
- }
- }
- continue;
- }
-
- if (instruction.isInvokeDirect()) {
- InvokeDirect invokeDirect = instruction.asInvokeDirect();
- DexMethod method = invokeDirect.getInvokedMethod();
- if (factory.isConstructor(method)) {
- continue;
- }
-
- DexClass clazz = appInfo.definitionForHolder(method, context);
- if (clazz == null) {
- // Report missing class since we don't know if it is an interface.
- warnMissingType(context, method.holder);
- } else if (clazz.isInterface()) {
- if (clazz.isLibraryClass()) {
- throw new CompilationError(
- "Unexpected call to a private method "
- + "defined in library class "
- + clazz.toSourceString(),
- getMethodOrigin(context.getReference()));
- }
- DexEncodedMethod directTarget = clazz.lookupMethod(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.
- if (directTarget.isPrivateMethod()) {
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- privateAsMethodOfCompanionClass(method),
- invokeDirect.outValue(),
- invokeDirect.arguments()));
- } else {
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- defaultAsMethodOfCompanionClass(method),
- invokeDirect.outValue(),
- invokeDirect.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.
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- defaultAsMethodOfCompanionClass(virtualTarget.getDefinition().method),
- invokeDirect.outValue(),
- invokeDirect.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;
- }
- }
- }
- }
-
- if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
- InvokeMethod invokeMethod = instruction.asInvokeMethod();
- DexMethod invokedMethod = invokeMethod.getInvokedMethod();
- DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
- if (emulatedItf != null) {
- // 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, invokeMethod.getInterfaceBit())
- .asSingleResolution();
- if (resolution != null
- && (resolution.getResolvedHolder().isLibraryClass()
- || appView.options().isDesugaredLibraryCompilation())) {
- rewriteCurrentInstructionToEmulatedInterfaceCall(
- emulatedItf, invokedMethod, invokeMethod, instructions);
- }
+ 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())) {
+ rewriteCurrentInstructionToEmulatedInterfaceCall(
+ emulatedItf, invokedMethod, invoke, instructions);
+ }
+ }
+
private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
// Here we try to avoid doing the expensive look-up on all invokes.
if (!emulatedMethods.contains(invokedMethod.name)) {