Enable horizontal class merging in disabled tests

Change-Id: I05c752631be63e498c86e6e83735a664cf3d9d49
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 89681f0..4c7570c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 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.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -116,9 +117,12 @@
           }
         }
 
-        if (appView.options().testing.enableInvokeSuperToInvokeVirtualRewriting) {
-          if (current.isInvokeSuper()) {
-            InvokeSuper invoke = current.asInvokeSuper();
+        if (current.isInvokeSuper()) {
+          InvokeSuper invoke = current.asInvokeSuper();
+
+          // Check if the instruction can be rewritten to invoke-super. This allows inlining of the
+          // enclosing method into contexts outside the current class.
+          if (appView.options().testing.enableInvokeSuperToInvokeVirtualRewriting) {
             DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
             if (singleTarget != null) {
               DexClass holder = appView.definitionForHolder(singleTarget, context);
@@ -134,10 +138,23 @@
               if (newSingleTarget == singleTarget) {
                 it.replaceCurrentInstruction(
                     new InvokeVirtual(invokedMethod, invoke.outValue(), invoke.arguments()));
+                continue;
               }
             }
-            continue;
           }
+
+          // Rebind the invoke to the most specific target.
+          DexMethod invokedMethod = invoke.getInvokedMethod();
+          DexClassAndMethod reboundTarget = rebindSuperInvokeToMostSpecific(invokedMethod, context);
+          if (reboundTarget != null && reboundTarget.getReference() != invokedMethod) {
+            it.replaceCurrentInstruction(
+                new InvokeSuper(
+                    reboundTarget.getReference(),
+                    invoke.outValue(),
+                    invoke.arguments(),
+                    reboundTarget.getHolder().isInterface()));
+          }
+          continue;
         }
 
         if (current.isInvokeVirtual()) {
@@ -274,6 +291,33 @@
     assert code.isConsistentSSA();
   }
 
+  /** This rebinds invoke-super instructions to their most specific target. */
+  private DexClassAndMethod rebindSuperInvokeToMostSpecific(
+      DexMethod target, ProgramMethod context) {
+    DexEncodedMethod definition = appView.appInfo().lookupSuperTarget(target, context);
+    if (definition == null) {
+      return null;
+    }
+
+    DexClass holder = appView.definitionFor(definition.getHolderType());
+    if (holder == null) {
+      assert false;
+      return null;
+    }
+
+    if (holder.isInterface() && holder.getType() != context.getHolder().superType) {
+      // Not allowed.
+      return null;
+    }
+
+    DexClassAndMethod method = DexClassAndMethod.create(holder, definition);
+    if (AccessControl.isMemberAccessible(method, holder, context, appView).isPossiblyFalse()) {
+      return null;
+    }
+
+    return method;
+  }
+
   /**
    * This rebinds invoke-virtual instructions to their most specific target.
    *
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index d288099..23ec0c9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -24,7 +24,6 @@
 import java.nio.file.Paths;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
@@ -187,7 +186,7 @@
   }
 
   @Test
-  public void outputIsIdentical() throws IOException, InterruptedException, ExecutionException {
+  public void outputIsIdentical() throws IOException {
     if (shouldCompileFail()) {
       // We expected an exception, but got none.
       // Return to ensure that this test fails due to the missing exception.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
index 0689a75..cf0d8ef 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -40,10 +40,6 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary
-            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
-            && !parameters.getDexRuntimeVersion().isDefault());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(CustomCollectionForwardingTest.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index ce07cbf..ac48a3d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -56,8 +56,6 @@
 
   @Test
   public void testCustomCollectionSuperCallsR8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult r8TestRunResult =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index c75123e..9d9b5cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -181,10 +181,6 @@
 
   @Test
   public void testStream() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary
-            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
-            && !parameters.getDexRuntimeVersion().isDefault());
     Assume.assumeFalse(
         "getAllFilesWithSuffixInDirectory() seems to find different files on Windows",
         ToolHelper.isWindows());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 406e16d..1452fd4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -33,6 +34,7 @@
 
   static class Sub extends Base {}
 
+  @NoHorizontalClassMerging
   static class EffectivelyFinal {}
 
   static class Reflection implements Callable<Class<?>> {
@@ -208,7 +210,6 @@
   @Test
   public void testR8() throws Exception {
     boolean isRelease = mode == CompilationMode.RELEASE;
-    expectThrowsWithHorizontalClassMergingIf(isRelease);
     boolean expectCallPresent = !isRelease;
     int expectedGetClassCount = isRelease ? 0 : 5;
     int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1;
@@ -216,6 +217,7 @@
         .setMode(mode)
         .addInnerClasses(GetClassTest.class)
         .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .noMinification()
         .addOptionsModification(this::configure)
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 816c073..4f65b48 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -3,12 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.assumenosideeffects;
 
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Collection;
 import org.junit.Test;
@@ -19,25 +21,6 @@
 public class AssumenosideeffectsPropagationTest extends TestBase {
   private static final Class<?> MAIN = SubsUser.class;
 
-  private static final String JVM_OUTPUT = StringUtils.lines(
-      "[Sub1, info]: message00",
-      "[Base1, debug]: message00",
-      "[Sub1, verbose]: message00",
-      "[Sub1, info]: message1",
-      "[Base1, debug]: message2",
-      "[Sub1, verbose]: message3",
-      "[Base2, info]: message08",
-      "[AnotherSub2, debug]: message08",
-      "[AnotherSub2, verbose]: message08",
-      "[Base2, info]: message4",
-      "[AnotherSub2, debug]: message5",
-      "[AnotherSub2, verbose]: message6",
-      "The end"
-  );
-  private static final String OUTPUT_WITHOUT_MESSAGES = StringUtils.lines(
-      "The end"
-  );
-
   enum TestConfig {
     SPECIFIC_RULES,
     NON_SPECIFIC_RULES_PARTIAL,
@@ -84,33 +67,40 @@
       }
     }
 
-    public String expectedOutput() {
+    public String expectedOutput(boolean enableHorizontalClassMerging) {
       switch (this) {
         case SPECIFIC_RULES:
-          return StringUtils.lines(
-              // Itf#info has side effects due to the missing Base2.
-              "[Sub1, info]: message00",
-              "[Base2, info]: message08",
-              // Base2#info also has side effects.
-              "[Base2, info]: message4",
-              "The end"
-          );
+          return enableHorizontalClassMerging
+              ? StringUtils.lines(
+                  "[Base2, info]: message08",
+                  // Base2#info also has side effects.
+                  "[Base2, info]: message4",
+                  "The end")
+              : StringUtils.lines(
+                  // Itf#info has side effects due to the missing Base2.
+                  "[Sub1, info]: message00",
+                  "[Base2, info]: message08",
+                  // Base2#info also has side effects.
+                  "[Base2, info]: message4",
+                  "The end");
         case NON_SPECIFIC_RULES_PARTIAL:
-          return StringUtils.lines(
-              // TODO(b/133208961): Introduce comparison/meet of assume rules.
-              // Itf has side effects for all methods, since we don't compute the meet yet.
-              "[Sub1, info]: message00",
-              "[Base1, debug]: message00",
-              "[Sub1, verbose]: message00",
-              "[Base2, info]: message08",
-              "[AnotherSub2, debug]: message08",
-              "[AnotherSub2, verbose]: message08",
-              // Base2#debug also has side effects.
-              "[AnotherSub2, debug]: message5",
-              "The end"
-          );
+          return enableHorizontalClassMerging
+              ? StringUtils.lines(
+                  "[AnotherSub2, debug]: message08", "[AnotherSub2, debug]: message5", "The end")
+              : StringUtils.lines(
+                  // TODO(b/133208961): Introduce comparison/meet of assume rules.
+                  // Itf has side effects for all methods, since we don't compute the meet yet.
+                  "[Sub1, info]: message00",
+                  "[Base1, debug]: message00",
+                  "[Sub1, verbose]: message00",
+                  "[Base2, info]: message08",
+                  "[AnotherSub2, debug]: message08",
+                  "[AnotherSub2, verbose]: message08",
+                  // Base2#debug also has side effects.
+                  "[AnotherSub2, debug]: message5",
+                  "The end");
         case NON_SPECIFIC_RULES_ALL:
-          return OUTPUT_WITHOUT_MESSAGES;
+          return StringUtils.lines("The end");
         default:
           throw new Unreachable();
       }
@@ -119,38 +109,60 @@
 
   private final TestParameters parameters;
   private final TestConfig config;
+  private final boolean enableHorizontalClassMerging;
 
   @Parameterized.Parameters(name = "{0} {1}")
   public static Collection<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        TestConfig.values(),
+        BooleanUtils.values());
   }
 
-  public AssumenosideeffectsPropagationTest(TestParameters parameters, TestConfig config) {
+  public AssumenosideeffectsPropagationTest(
+      TestParameters parameters, TestConfig config, boolean enableHorizontalClassMerging) {
     this.parameters = parameters;
     this.config = config;
+    this.enableHorizontalClassMerging = enableHorizontalClassMerging;
   }
 
   @Test
   public void testJVMOutput() throws Exception {
-    assumeTrue(parameters.isCfRuntime() && config == TestConfig.SPECIFIC_RULES);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(config == TestConfig.SPECIFIC_RULES);
+    assumeFalse(enableHorizontalClassMerging);
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutput(JVM_OUTPUT);
+        .assertSuccessWithOutputLines(
+            "[Sub1, info]: message00",
+            "[Base1, debug]: message00",
+            "[Sub1, verbose]: message00",
+            "[Sub1, info]: message1",
+            "[Base1, debug]: message2",
+            "[Sub1, verbose]: message3",
+            "[Base2, info]: message08",
+            "[AnotherSub2, debug]: message08",
+            "[AnotherSub2, verbose]: message08",
+            "[Base2, info]: message4",
+            "[AnotherSub2, debug]: message5",
+            "[AnotherSub2, verbose]: message6",
+            "The end");
   }
 
   @Test
   public void testR8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(config != TestConfig.NON_SPECIFIC_RULES_ALL);
     testForR8(parameters.getBackend())
         .addInnerClasses(AssumenosideeffectsPropagationTest.class)
         .addKeepMainRule(MAIN)
         .addKeepRules(config.getKeepRules())
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
         .enableInliningAnnotations()
         .noMinification()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutput(config.expectedOutput());
+        .assertSuccessWithOutput(config.expectedOutput(enableHorizontalClassMerging));
   }
 
   interface Itf {