Handle invoke-super to private interface method in same nest

Bug: 216694478
Bug: 145775365
Change-Id: I6b70d51193d10092d7971bfab29bd65ff8bcd900
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 1f845ed..113c31c 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -361,6 +361,10 @@
         return null;
       }
 
+      if (getResolvedHolder().isInterface() && getResolvedMethod().isPrivate()) {
+        return getResolutionPair();
+      }
+
       // The symbolic reference is the holder type that resolution was initiated at.
       DexClass symbolicReference = initialResolutionHolder;
 
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 3155b54..813a237 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -990,25 +991,42 @@
                 && !virtualMethods.containsKey(wrapped);
           };
 
-      for (DexEncodedMethod directMethod : source.directMethods()) {
-        if (directMethod.isInstanceInitializer()) {
-          DexEncodedMethod resultingConstructor =
-              renameConstructor(directMethod, availableMethodSignatures);
-          add(directMethods, resultingConstructor, MethodSignatureEquivalence.get());
-          blockRedirectionOfSuperCalls(resultingConstructor.getReference());
-        } else {
-          DexEncodedMethod resultingDirectMethod =
-              renameMethod(
-                  directMethod,
-                  availableMethodSignatures,
-                  directMethod.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
-          add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
-          deferredRenamings.map(directMethod.getReference(), resultingDirectMethod.getReference());
-          deferredRenamings.recordMove(
-              directMethod.getReference(), resultingDirectMethod.getReference());
-          blockRedirectionOfSuperCalls(resultingDirectMethod.getReference());
-        }
-      }
+      source.forEachProgramDirectMethod(
+          directMethod -> {
+            DexEncodedMethod definition = directMethod.getDefinition();
+            if (definition.isInstanceInitializer()) {
+              DexEncodedMethod resultingConstructor =
+                  renameConstructor(definition, availableMethodSignatures);
+              add(directMethods, resultingConstructor, MethodSignatureEquivalence.get());
+              blockRedirectionOfSuperCalls(resultingConstructor.getReference());
+            } else {
+              DexEncodedMethod resultingDirectMethod =
+                  renameMethod(
+                      definition,
+                      availableMethodSignatures,
+                      definition.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
+              add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
+              deferredRenamings.map(
+                  directMethod.getReference(), resultingDirectMethod.getReference());
+              deferredRenamings.recordMove(
+                  directMethod.getReference(), resultingDirectMethod.getReference());
+              blockRedirectionOfSuperCalls(resultingDirectMethod.getReference());
+
+              // Private methods in the parent class may be targeted with invoke-super if the two
+              // classes are in the same nest. Ensure such calls are mapped to invoke-direct.
+              if (definition.isInstance()
+                  && definition.isPrivate()
+                  && AccessControl.isMemberAccessible(directMethod, source, target, appView)
+                      .isTrue()) {
+                deferredRenamings.mapVirtualMethodToDirectInType(
+                    directMethod.getReference(),
+                    prototypeChanges ->
+                        new MethodLookupResult(
+                            resultingDirectMethod.getReference(), null, DIRECT, prototypeChanges),
+                    target.getType());
+              }
+            }
+          });
 
       for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
         DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index 3803d1a..898cfae 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -224,8 +224,8 @@
   static class A implements I {
     public void foo() {
       // Rewritten to invoke-special A.bar or I.bar which resolves to private method A.bar
-      // When targeting B.bar => throws NoSuchMethodError.
-      // When targeting A.bar:
+      // When targeting A.bar => throws NoSuchMethodError.
+      // When targeting I.bar:
       //   - in same nest => success.
       //   - not in nest => throws IllegalAccessError.
       bar();