Version 1.2.37

Merge: Introduce relaxed method signature equivalence.
CL: https://r8-review.googlesource.com/c/r8/+/24880

Merge: Reproduce b/112185748.
CL: https://r8-review.googlesource.com/c/r8/+/24800

and then adjust tests a bit as code inspector refactoring is not there.

Bug: 112185748
Change-Id: I7b3446e72aef23a372dd7d80973e51db6652e652
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 73f0611..9e967a2 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.2.36";
+  public static final String LABEL = "1.2.37";
 
   private Version() {
   }
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
new file mode 100644
index 0000000..7ba5334
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
@@ -0,0 +1,135 @@
+// 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.naming;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+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.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+interface SuperInterface {
+  Super foo();
+}
+
+interface SubInterface extends SuperInterface {
+  @Override
+  Sub foo();
+}
+
+class Super {
+  protected int bar() {
+    return 0;
+  }
+}
+
+class Sub extends Super {
+  @Override
+  protected int bar() {
+    return 1;
+  }
+}
+
+class SuperImplementer implements SuperInterface {
+  @Override
+  public Super foo() {
+    return new Sub();
+  }
+}
+
+class SubImplementer extends SuperImplementer implements SubInterface {
+  @Override
+  public Sub foo() {
+    return (Sub) super.foo();
+  }
+}
+
+class TestMain {
+  public static void main(String[] args) {
+    SubImplementer subImplementer = new SubImplementer();
+    Super sup = subImplementer.foo();
+    System.out.print(sup.bar());
+  }
+}
+
+@RunWith(VmTestRunner.class)
+public class CovariantReturnTypeInSubInterfaceTest extends TestBase {
+
+  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",
+        aggressive,
+        "-keep class " + mainName + " {",
+        "  public void main(...);",
+        "}",
+        "-keep,allowobfuscation class **.Super* {",
+        "  <methods>;",
+        "}",
+        "-keep,allowobfuscation class **.Sub* {",
+        "  <methods>;",
+        "}"
+    );
+    AndroidApp app = readClasses(
+        SuperInterface.class,
+        SubInterface.class,
+        Super.class,
+        Sub.class,
+        SuperImplementer.class,
+        SubImplementer.class,
+        TestMain.class
+    );
+    AndroidApp processedApp =
+        compileWithR8(app, String.join(System.lineSeparator(), config), options -> {
+          options.enableInlining = false;
+        });
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject superInterface = inspector.clazz(SuperInterface.class);
+    assertThat(superInterface, isRenamed());
+    MethodSubject foo1 = superInterface.method(
+        Super.class.getCanonicalName(), "foo", ImmutableList.of());
+    assertThat(foo1, isRenamed());
+    ClassSubject subInterface = inspector.clazz(SubInterface.class);
+    assertThat(subInterface, isRenamed());
+    MethodSubject foo2 = subInterface.method(
+        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 c7c74e9..de70177 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -16,6 +16,7 @@
 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;
@@ -23,7 +24,9 @@
 import java.nio.file.Paths;
 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;