Fix inadequate argument propagation from interface methods to overrides
Bug: 230166353
Change-Id: Ida034b1eea3f55c0dac7b90c87b0f8700d6cc636
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index 787ae13..e0d2ac0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -60,9 +60,6 @@
// memory usage, but would require visiting all transitive (program) super classes for each
// subclass.
private void addParentState(DexProgramClass clazz, DexProgramClass superclass) {
- ClassTypeElement classType =
- TypeElement.fromDexType(clazz.getType(), maybeNull(), appView).asClassType();
-
PropagationState parentState = propagationStates.get(superclass.asProgramClass());
assert parentState != null;
@@ -87,7 +84,7 @@
parentState.inactiveUntilUpperBound.forEach(
(bounds, inactiveMethodStates) -> {
ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
- if (upperBound.equalUpToNullability(classType)) {
+ if (shouldActivateMethodStateGuardedByBounds(upperBound, clazz, superclass)) {
// The upper bound is the current class, thus this inactive information now becomes
// active.
if (bounds.hasDynamicLowerBoundType()) {
@@ -158,6 +155,20 @@
}
return methodState;
}
+
+ private boolean shouldActivateMethodStateGuardedByBounds(
+ ClassTypeElement upperBound, DexProgramClass currentClass, DexProgramClass superClass) {
+ ClassTypeElement classType =
+ TypeElement.fromDexType(currentClass.getType(), maybeNull(), appView).asClassType();
+ // When propagating argument information for interface methods downwards from an interface to
+ // a non-interface we need to account for the parent classes of the current class.
+ if (superClass.isInterface()
+ && !currentClass.isInterface()
+ && currentClass.getSuperType() != appView.dexItemFactory().objectType) {
+ return classType.lessThanOrEqualUpToNullability(upperBound, appView);
+ }
+ return classType.equalUpToNullability(upperBound);
+ }
}
// For each class, stores the argument information for each virtual method on this class and all
@@ -224,7 +235,7 @@
// TODO(b/190154391): Verify that the bounds are not trivial according to the
// static receiver type.
ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
- if (isUpperBoundSatisfied(upperBound, clazz, propagationState)) {
+ if (isUpperBoundSatisfied(upperBound, clazz)) {
if (bounds.hasDynamicLowerBoundType()) {
// TODO(b/190154391): Verify that the lower bound is a subtype of the current
// class.
@@ -255,10 +266,7 @@
propagationStates.put(clazz, propagationState);
}
- private boolean isUpperBoundSatisfied(
- ClassTypeElement upperBound,
- DexProgramClass currentClass,
- PropagationState propagationState) {
+ private boolean isUpperBoundSatisfied(ClassTypeElement upperBound, DexProgramClass currentClass) {
DexType upperBoundType =
upperBound.getClassType() == appView.dexItemFactory().objectType
&& upperBound.getInterfaces().hasSingleKnownInterface()
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
index 2fa3dd5..836c703 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/InterfaceInvokeWithNonTrivialButImpreciseStaticTypeTest.java
@@ -35,8 +35,7 @@
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- // TODO(b/229951611): Should succeed with "B".
- .assertFailureWithErrorThatThrows(NullPointerException.class);
+ .assertSuccessWithOutputLines("B");
}
static class Main {