Disallow rewrite of invoke-super in merged class

When we merge a class B into another class C, the methods from B are moved into C, and then we subsequently rewrite the invoke-super instructions in C that hit a method in B, such that they use an invoke-direct instruction instead. In this process, we need to avoid rewriting the invoke-super instructions that originally was in the superclass B.

Example:
  class A {
    public void m() {}
  }
  class B extends A {
    // this method will be made private and renamed to C.m$B when merged into C
    public void m() {
      // invoke must not be rewritten to "invoke-direct C.m$B"
      // (this would lead to an infinite loop)
      super.m();
    }
  }
  class C extends B {
    // invoke needs to be rewritten to "invoke-direct C.m$B"
    public void m() { super.m(); }
  }

Change-Id: Ic8698193aeeca7b9293d0214e0556c9407540cc9
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 4b57209..dbd67dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -432,6 +432,10 @@
           directMethods.add(renameConstructor(directMethod));
         } else {
           directMethods.add(directMethod);
+
+          if (!directMethod.isStaticMethod()) {
+            blockRedirectionOfSuperCalls(directMethod.method);
+          }
         }
       }
 
@@ -467,6 +471,7 @@
         // Record that invoke-super instructions in the target class should be redirected to the
         // newly created direct method.
         redirectSuperCallsInTarget(virtualMethod.method, resultingDirectMethod.method);
+        blockRedirectionOfSuperCalls(resultingDirectMethod.method);
 
         if (shadowedBy == null) {
           // In addition to the newly added direct method, create a virtual method such that we do
@@ -490,6 +495,7 @@
               (existing, method) -> {
                 DexEncodedMethod renamedMethod = renameMethod(method, target.type, true);
                 deferredRenamings.map(method.method, renamedMethod.method);
+                blockRedirectionOfSuperCalls(renamedMethod.method);
                 return renamedMethod;
               });
       Collection<DexEncodedMethod> mergedVirtualMethods =
@@ -578,6 +584,26 @@
       }
     }
 
+    private void blockRedirectionOfSuperCalls(DexMethod method) {
+      // We are merging a class B into C. The methods from B are being moved into C, and then we
+      // subsequently rewrite the invoke-super instructions in C that hit a method in B, such that
+      // they use an invoke-direct instruction instead. In this process, we need to avoid rewriting
+      // the invoke-super instructions that originally was in the superclass B.
+      //
+      // Example:
+      //   class A {
+      //     public void m() {}
+      //   }
+      //   class B extends A {
+      //     public void m() { super.m(); } <- invoke must not be rewritten to invoke-direct
+      //                                       (this would lead to an infinite loop)
+      //   }
+      //   class C extends B {
+      //     public void m() { super.m(); } <- invoke needs to be rewritten to invoke-direct
+      //   }
+      deferredRenamings.markMethodAsMerged(method);
+    }
+
     private boolean resolutionSucceeds(DexMethod targetMethod) {
       DexClass enclosingClass = appInfo.definitionFor(targetMethod.holder);
       return enclosingClass != null
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index ea57d1d..942a857 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -45,16 +45,19 @@
 
   private final Map<DexField, DexField> fieldMap;
   private final Map<DexMethod, DexMethod> methodMap;
+  private final Set<DexMethod> mergedMethods;
   private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps;
 
   public VerticalClassMergerGraphLense(
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
+      Set<DexMethod> mergedMethods,
       Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps,
       GraphLense previousLense) {
     this.previousLense = previousLense;
     this.fieldMap = fieldMap;
     this.methodMap = methodMap;
+    this.mergedMethods = mergedMethods;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
   }
 
@@ -67,7 +70,7 @@
   public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
     assert isContextFreeForMethod(method) || (context != null && type != null);
     DexMethod previous = previousLense.lookupMethod(method, context, type);
-    if (type == Type.SUPER) {
+    if (type == Type.SUPER && !mergedMethods.contains(context.method)) {
       Map<DexMethod, DexMethod> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context.method.holder);
       if (virtualToDirectMethodMap != null) {
@@ -127,6 +130,7 @@
     private final ImmutableMap.Builder<DexField, DexField> fieldMapBuilder = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod> methodMapBuilder =
         ImmutableMap.builder();
+    private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
     private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
         new HashMap<>();
 
@@ -141,7 +145,15 @@
         return previousLense;
       }
       return new VerticalClassMergerGraphLense(
-          fieldMap, methodMap, contextualVirtualToDirectMethodMaps, previousLense);
+          fieldMap,
+          methodMap,
+          mergedMethodsBuilder.build(),
+          contextualVirtualToDirectMethodMaps,
+          previousLense);
+    }
+
+    public void markMethodAsMerged(DexMethod method) {
+      mergedMethodsBuilder.add(method);
     }
 
     public void map(DexField from, DexField to) {
@@ -161,6 +173,7 @@
     public void merge(VerticalClassMergerGraphLense.Builder builder) {
       fieldMapBuilder.putAll(builder.fieldMapBuilder.build());
       methodMapBuilder.putAll(builder.methodMapBuilder.build());
+      mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
         Map<DexMethod, DexMethod> current = contextualVirtualToDirectMethodMaps.get(context);
         Map<DexMethod, DexMethod> other = builder.contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 58b87c0..1e71a82 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -289,7 +289,9 @@
             "-keep public class classmerging.pkg.SimpleInterfaceImplRetriever"));
   }
 
-  @Ignore("b/73958515")
+  // TODO(christofferqa): This test checks that the invoke-super instruction in B is not rewritten
+  // into an invoke-direct instruction after it gets merged into class C. We should add a test that
+  // checks that this works with and without inlining of the method B.m().
   @Test
   public void testRewritePinnedMethod() throws Exception {
     String main = "classmerging.RewritePinnedMethodTest";