Link default interface methods to their implementation methods.

Bug: 128667509
Bug: 240134352
Change-Id: Ia4619b2ad93b26ceff9958ccc4600a6f98fb0041
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0ba6cb1..ad5a184 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -106,6 +106,8 @@
   private OptimizationInfo optimizationInfo = DefaultOptimizationInfoImpl.DEFAULT_INSTANCE;
   private int classFileVersion = -1;
 
+  private DexEncodedMethod defaultInterfaceMethodImplementation = null;
+
   // This flag indicates the current instance is no longer up-to-date as another instance was
   // created based on this. Any further (public) operations on this instance will raise an error
   // to catch potential bugs due to the inconsistency (e.g., http://b/111893131)
@@ -128,6 +130,21 @@
     obsolete = true;
   }
 
+  public DexEncodedMethod getDefaultInterfaceMethodImplementation() {
+    return defaultInterfaceMethodImplementation;
+  }
+
+  public void setDefaultInterfaceMethodImplementation(DexEncodedMethod implementation) {
+    assert defaultInterfaceMethodImplementation == null;
+    assert implementation != null;
+    assert code != null;
+    assert code == implementation.getCode();
+    assert code.getOwner() == implementation;
+    accessFlags.setAbstract();
+    removeCode();
+    defaultInterfaceMethodImplementation = implementation;
+  }
+
   /**
    * Flags this method as no longer being obsolete.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 3976ad7..befc0c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -289,12 +289,15 @@
     }
   }
 
+  public static String getCompanionClassDescriptor(String descriptor) {
+    return descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";";
+  }
+
   // Gets the companion class for the interface `type`.
   final DexType getCompanionClassType(DexType type) {
     assert type.isClassType();
     String descriptor = type.descriptor.toString();
-    String ccTypeDescriptor = descriptor.substring(0, descriptor.length() - 1)
-        + COMPANION_CLASS_NAME_SUFFIX + ";";
+    String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
     return factory.createType(ccTypeDescriptor);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index eabaec5..db93b99 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -93,12 +93,9 @@
         assert (dexCode.getDebugInfo() == null)
             || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
 
-        // Make the method abstract.
-        virtual.accessFlags.setAbstract();
-        virtual.removeCode(); // Remove code first to void ownership.
-
         DexEncodedMethod implMethod = new DexEncodedMethod(
             companionMethod, newFlags, virtual.annotations, virtual.parameterAnnotationsList, code);
+        virtual.setDefaultInterfaceMethodImplementation(implMethod);
         companionMethods.add(implMethod);
         graphLensBuilder.move(virtual.method, implMethod.method);
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 64624d3..6585e69 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1629,9 +1629,19 @@
       markVirtualMethodAsReachable(target.method, holder.accessFlags.isInterface(), reason);
       // Reachability for default methods is based on live subtypes in general. For keep rules,
       // we need special handling as we essentially might have live subtypes that are outside of
-      // our reach. Do this here, as we still know that this is due to a keep rule.
-      if (holder.isInterface() && target.isNonAbstractVirtualMethod()) {
-        markVirtualMethodAsLive(target, reason);
+      // the current compilation unit. Keep either the default-method or its implementation method.
+      // TODO(b/120959039): Codify the kept-graph expectations for these cases in tests.
+      if (holder.isInterface() && target.isVirtualMethod()) {
+        if (target.isNonAbstractVirtualMethod()) {
+          markVirtualMethodAsLive(target, reason);
+        } else {
+          DexEncodedMethod implementation = target.getDefaultInterfaceMethodImplementation();
+          if (implementation != null) {
+            DexClass companion = appView.definitionFor(implementation.method.holder);
+            markTypeAsLive(companion.type);
+            markVirtualMethodAsLive(implementation, reason);
+          }
+        }
       }
     } else {
       markDirectStaticOrConstructorMethodAsLive(target, reason);
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
index 54245af..2091155 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming.applymapping.desugar;
 
+import static com.android.tools.r8.references.Reference.classFromClass;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
@@ -11,8 +13,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -70,12 +76,25 @@
 
   @Test
   public void testLibraryLinkedWithProgram() throws Throwable {
+    String ruleContent = "-keep class " + LibraryInterface.class.getTypeName() + " { *; }";
     R8TestCompileResult libraryResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(LibraryInterface.class)
-            .addKeepRules("-keep class " + LibraryInterface.class.getTypeName() + " { *; }")
+            .addKeepRules(ruleContent)
             .apply(parameters::setMinApiForRuntime)
             .compile();
+    CodeInspector inspector = libraryResult.inspector();
+    assertTrue(inspector.clazz(LibraryInterface.class).isPresent());
+    assertTrue(inspector.method(LibraryInterface.class.getMethod("foo")).isPresent());
+    if (willDesugarDefaultInterfaceMethods(parameters.getRuntime())) {
+      ClassSubject companion = inspector.clazz(Reference.classFromDescriptor(
+          InterfaceMethodRewriter.getCompanionClassDescriptor(
+              classFromClass(LibraryInterface.class).getDescriptor())));
+      // Check that we included the companion class.
+      assertTrue(companion.isPresent());
+      // TODO(b/129223905): Check the method is also present on the companion class.
+      assertTrue(inspector.method(LibraryInterface.class.getMethod("foo")).isPresent());
+    }
 
     R8TestRunResult result =
         testForR8(parameters.getBackend())