Avoid vertical class merging if virtual method is hit by invoke-direct
It is valid to have an invoke-direct instruction in a default interface method that targets another default method in the same interface (see InterfaceMethodDesugaringTests.testInvokeSpecialToDefaultMethod). However, in a class, that would lead to a verification error. Therefore, this CL disallows merging such interfaces into their subtypes.
Note that, during vertical class merging, we create a private method for each virtual method that is moved to the subtype. Therefore, it would be possible to redirect the invoke-direct instructions that target a virtual method to invoke-direct instructions that target the newly created private methods. However, this would require maintaining potentially many mappings in the graph lense that would likely never be used in practice.
Change-Id: I24ec192c25d4357fa8582edffc6db80ff628e7e0
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 061b9bf..7df9d9c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -153,6 +153,8 @@
* its implementation may be removed and it may be marked abstract.
*/
private final SetWithReason<DexEncodedMethod> targetedMethods = new SetWithReason<>();
+ /** Set of virtual methods that are the immediate target of an invoke-direct. */
+ private final Set<DexMethod> virtualMethodsTargetedByInvokeDirect = Sets.newIdentityHashSet();
/**
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
@@ -601,6 +603,13 @@
DexEncodedMethod target = appInfo.dispatchDirectInvoke(resolutionResult);
if (target != null) {
markDirectStaticOrConstructorMethodAsLive(target, reason);
+
+ // It is valid to have an invoke-direct instruction in a default interface method that
+ // targets another default method in the same interface (see testInvokeSpecialToDefault-
+ // Method). In a class, that would lead to a verification error.
+ if (target.isVirtualMethod()) {
+ virtualMethodsTargetedByInvokeDirect.add(target.method);
+ }
}
}
@@ -1479,6 +1488,8 @@
* removed.
*/
final SortedSet<DexMethod> targetedMethods;
+ /** Set of virtual methods that are the immediate target of an invoke-direct. */
+ final SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect;
/**
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
@@ -1596,6 +1607,9 @@
ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedLambdas.getItems());
this.targetedMethods = toSortedDescriptorSet(enqueuer.targetedMethods.getItems());
+ this.virtualMethodsTargetedByInvokeDirect =
+ ImmutableSortedSet.copyOf(
+ DexMethod::slowCompareTo, enqueuer.virtualMethodsTargetedByInvokeDirect);
this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
this.instanceFieldReads = enqueuer.collectInstanceFieldsRead();
@@ -1636,6 +1650,7 @@
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
+ this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
this.liveMethods = previous.liveMethods;
this.liveFields = previous.liveFields;
this.instanceFieldReads = previous.instanceFieldReads;
@@ -1675,6 +1690,8 @@
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = rewriteMethodsConservatively(previous.targetedMethods, lense);
+ this.virtualMethodsTargetedByInvokeDirect =
+ rewriteMethodsConservatively(previous.virtualMethodsTargetedByInvokeDirect, lense);
this.liveMethods = rewriteMethodsConservatively(previous.liveMethods, lense);
this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
this.instanceFieldReads =
@@ -1726,6 +1743,7 @@
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
+ this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
this.liveMethods = previous.liveMethods;
this.liveFields = previous.liveFields;
this.instanceFieldReads = previous.instanceFieldReads;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 0840d91..38473f3 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -81,6 +81,8 @@
RESOLUTION_FOR_FIELDS_MAY_CHANGE,
RESOLUTION_FOR_METHODS_MAY_CHANGE,
STATIC_INITIALIZERS,
+ UNHANDLED_INVOKE_DIRECT,
+ UNHANDLED_INVOKE_SUPER,
UNSAFE_INLINING,
UNSUPPORTED_ATTRIBUTES;
@@ -121,6 +123,12 @@
case STATIC_INITIALIZERS:
message = "merging of static initializers are not supported";
break;
+ case UNHANDLED_INVOKE_DIRECT:
+ message = "a virtual method is targeted by an invoke-direct instruction";
+ break;
+ case UNHANDLED_INVOKE_SUPER:
+ message = "it may change the semantics of an invoke-super instruction";
+ break;
case UNSAFE_INLINING:
message = "force-inlining might fail";
break;
@@ -217,10 +225,15 @@
// }
// }
for (DexMethod signature : appInfo.brokenSuperInvokes) {
- DexClass targetClass = appInfo.definitionFor(signature.holder);
- if (targetClass != null && targetClass.isProgramClass()) {
- pinnedTypes.add(signature.holder);
- }
+ markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_SUPER);
+ }
+
+ // It is valid to have an invoke-direct instruction in a default interface method that targets
+ // another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
+ // SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
+ // Therefore, we disallow merging such interfaces into their subtypes.
+ for (DexMethod signature : appInfo.virtualMethodsTargetedByInvokeDirect) {
+ markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_DIRECT);
}
}