Don't vertical merge classes that lead to resolution error changes.
The workaround for the Dalvik verifier issue of not allowing an abstract method
on a non-abstract class did not preserve the expected AbstractMethodError.
Bug: 144085169
Change-Id: I5ec96dba37e0620e0ba05f27de02d2a4381a2174
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 b51c78d..f07499b 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -994,15 +994,15 @@
// Non-abstract methods are handled below (they cannot simply be moved to the subclass as
// a virtual method, because they might be the target of an invoke-super instruction).
if (virtualMethod.accessFlags.isAbstract()) {
+ // Abort if target is non-abstract and does not override the abstract method.
+ if (!target.isAbstract()) {
+ assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
+ abortMerge = true;
+ return false;
+ }
// Update the holder of [virtualMethod] using renameMethod().
DexEncodedMethod resultingVirtualMethod =
renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
- if (appView.options().canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
- && !target.isAbstract()) {
- resultingVirtualMethod.accessFlags.unsetAbstract();
- resultingVirtualMethod =
- resultingVirtualMethod.toEmptyThrowingMethod(appView.options());
- }
deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 8faa274..cd14b54 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1005,6 +1005,7 @@
public boolean trackDesugaredAPIConversions =
System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
public boolean keepInheritedInterfaceMethods = false;
+ public boolean allowNonAbstractClassesWithAbstractMethods = false;
// Flag to turn on/off JDK11+ nest-access control even when not required (Cf backend)
public boolean enableForceNestBasedAccessDesugaringForTest = false;
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index f5a99fa..30295fd 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
@@ -65,7 +64,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
}
@Test
@@ -75,16 +74,13 @@
.addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
+ .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
}
- private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
- if (isR8
- && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
- // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
- return containsString("NullPointerException");
- }
+ private Matcher<String> getExpectedErrorMatcher() {
if (parameters.isDexRuntime()
&& parameters
.getRuntime()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index bd2d4ac..6c4070e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
@@ -65,7 +64,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
}
@Test
@@ -75,16 +74,13 @@
.addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
+ .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
}
- private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
- if (isR8
- && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
- // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
- return containsString("NullPointerException");
- }
+ private Matcher<String> getExpectedErrorMatcher() {
if (parameters.isDexRuntime()
&& parameters
.getRuntime()