Introduce relaxed method signature equivalence.

This equivalence is in between MethodJavaSignatureEquivalence
and MethodSignatureEquivalence: it is stronger than the former in that
it considers the return type of methods; it is weaker than the latter
in that it allows covariant return type: a method in a sub type can
return a narrower type.

Method minifier uses this equivalence for non aggressive renaming,
which still satisfies multi interfaces and lambda renaming that
require fine-grained method wrapping as well as satisfies the case
with covariant return type that requires less strict wrapping.

Bug: 112185748, 76461545
Change-Id: Ib462dbd81e598e4b11b7f787da4f5cfe3e3bca3b
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index c6729e8..97f1228 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.MethodSignatureRelaxedEquivalence;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
@@ -90,11 +91,15 @@
  */
 class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> {
 
-  private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+  private final Equivalence<DexMethod> equivalence;
   private final Map<DexCallSite, DexString> callSiteRenaming = new IdentityHashMap<>();
 
   MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) {
     super(appInfo, rootSet, options);
+    equivalence =
+        overloadAggressively
+            ? MethodSignatureEquivalence.get()
+            : new MethodSignatureRelaxedEquivalence(appInfo);
   }
 
   @Override
@@ -337,10 +342,12 @@
   }
 
   private void addStatesToGlobalMapForMethod(
-      DexEncodedMethod method, Set<NamingState<DexProto, ?>> collectedStates,
+      DexEncodedMethod method,
+      Set<NamingState<DexProto, ?>> collectedStates,
       Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap,
       Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap,
-      Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates, DexType originInterface) {
+      Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates,
+      DexType originInterface) {
     Wrapper<DexMethod> key = equivalence.wrap(method.method);
     Set<NamingState<DexProto, ?>> stateSet =
         globalStateMap.computeIfAbsent(key, k -> new HashSet<>());
diff --git a/src/main/java/com/android/tools/r8/utils/MethodSignatureRelaxedEquivalence.java b/src/main/java/com/android/tools/r8/utils/MethodSignatureRelaxedEquivalence.java
new file mode 100644
index 0000000..74d2d98
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MethodSignatureRelaxedEquivalence.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.base.Equivalence;
+
+/**
+ * Implements an equivalence on {@link DexMethod} that does not take the holder into account but
+ * allow covariant return type: a method in a sub type can return a narrower type.
+ */
+public class MethodSignatureRelaxedEquivalence extends Equivalence<DexMethod> {
+
+  private final AppInfo appInfo;
+
+  public MethodSignatureRelaxedEquivalence(AppInfo appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  @Override
+  protected boolean doEquivalent(DexMethod a, DexMethod b) {
+    return a.name.equals(b.name) && a.proto.parameters.equals(b.proto.parameters)
+        && (a.proto.returnType.equals(b.proto.returnType)
+            || (a.proto.returnType.isStrictSubtypeOf(b.proto.returnType, appInfo)
+                && a.getHolder().isStrictSubtypeOf(b.getHolder(), appInfo))
+            || (b.proto.returnType.isStrictSubtypeOf(a.proto.returnType, appInfo)
+                && b.getHolder().isStrictSubtypeOf(a.getHolder(), appInfo)));
+  }
+
+  @Override
+  protected int doHash(DexMethod method) {
+    return method.name.hashCode() * 31 + method.proto.parameters.hashCode();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
index 6602d0c..20f46b4 100644
--- a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
@@ -7,15 +7,20 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
 import java.util.List;
-import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 interface SuperInterface {
   Super foo();
@@ -42,14 +47,14 @@
 class SuperImplementer implements SuperInterface {
   @Override
   public Super foo() {
-    return new Super();
+    return new Sub();
   }
 }
 
-class SubImplementer implements SubInterface {
+class SubImplementer extends SuperImplementer implements SubInterface {
   @Override
   public Sub foo() {
-    return new Sub();
+    return (Sub) super.foo();
   }
 }
 
@@ -57,19 +62,22 @@
   public static void main(String[] args) {
     SubImplementer subImplementer = new SubImplementer();
     Super sup = subImplementer.foo();
-    System.out.println(sup.bar());
+    System.out.print(sup.bar());
   }
 }
 
+@RunWith(VmTestRunner.class)
 public class CovariantReturnTypeInSubInterfaceTest extends TestBase {
 
-  @Ignore("b/112185748")
-  @Test
-  public void test() throws Exception {
+  private void test(boolean overloadAggressively) throws Exception {
+    String mainName = TestMain.class.getCanonicalName();
+    String aggressive =
+        overloadAggressively ? "-overloadaggressively" : "# Not overload aggressively";
     List<String> config = ImmutableList.of(
         "-printmapping",
         "-useuniqueclassmembernames",
-        "-keep class " + TestMain.class.getCanonicalName() + " {",
+        aggressive,
+        "-keep class " + mainName + " {",
         "  public void main(...);",
         "}",
         "-keep,allowobfuscation class **.Super* {",
@@ -88,7 +96,10 @@
         SubImplementer.class,
         TestMain.class
     );
-    AndroidApp processedApp = compileWithR8(app, String.join(System.lineSeparator(), config));
+    AndroidApp processedApp =
+        compileWithR8(app, String.join(System.lineSeparator(), config), options -> {
+          options.enableInlining = false;
+        });
     CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject superInterface = inspector.clazz(SuperInterface.class);
     assertThat(superInterface, isRenamed());
@@ -101,6 +112,24 @@
         Sub.class.getCanonicalName(), "foo", ImmutableList.of());
     assertThat(foo2, isRenamed());
     assertEquals(foo1.getFinalName(), foo2.getFinalName());
+
+    ProcessResult javaResult = ToolHelper.runJava(ToolHelper.getClassPathForTests(), mainName);
+    assertEquals(0, javaResult.exitCode);
+    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+    processedApp.writeToZip(outDex, OutputMode.DexIndexed);
+    ProcessResult artResult = ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), mainName);
+    assertEquals(0, artResult.exitCode);
+    assertEquals(javaResult.stdout, artResult.stdout);
+  }
+
+  @Test
+  public void test_notAggressively() throws Exception {
+    test(false);
+  }
+
+  @Test
+  public void test_aggressively() throws Exception {
+    test(true);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
index e1fa3d0..ef6e0c8 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -14,12 +14,15 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.origin.Origin;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(VmTestRunner.class)
 public class InterfaceRenamingTestRunner extends TestBase {
   static final Class CLASS = InterfaceRenamingTest.class;
   static final Class[] CLASSES = InterfaceRenamingTest.CLASSES;
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
index 02bca3a..56dce3a 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -16,13 +16,16 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(VmTestRunner.class)
 public class LambdaRenamingTestRunner extends TestBase {
   static final Class CLASS = LambdaRenamingTest.class;
   static final Class[] CLASSES = LambdaRenamingTest.CLASSES;