Add virtual method horizontal class merging retrace test

Bug: 165000217
Bug: 163311975

Change-Id: I1672c2e61ec1384e1d8380909f36513362b6e708
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 28dc9b0..14488b6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -175,6 +175,13 @@
     DexMethod templateReference = methods.iterator().next().getReference();
     DexMethod originalMethodReference =
         appView.graphLens().getOriginalMethodSignature(templateReference);
+    DexMethod bridgeMethodReference =
+        dexItemFactory.createFreshMethodName(
+            originalMethodReference.getName().toSourceString() + "$bridge",
+            null,
+            originalMethodReference.proto,
+            originalMethodReference.getHolderType(),
+            tryMethod -> target.lookupMethod(tryMethod) == null);
 
     DexMethod newMethodReference =
         dexItemFactory.createMethod(target.type, templateReference.proto, templateReference.name);
@@ -184,7 +191,7 @@
             classIdField,
             superMethod,
             newMethodReference,
-            originalMethodReference);
+            bridgeMethodReference);
     DexEncodedMethod newMethod =
         new DexEncodedMethod(
             newMethodReference,
@@ -200,7 +207,7 @@
     for (ProgramMethod oldMethod : methods) {
       lensBuilder.moveMethod(oldMethod.getReference(), newMethodReference);
     }
-    lensBuilder.recordExtraOriginalSignature(originalMethodReference, newMethodReference);
+    lensBuilder.recordExtraOriginalSignature(bridgeMethodReference, newMethodReference);
 
     target.addVirtualMethod(newMethod);
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java
index 7dae8b6..565c1bd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java
@@ -44,16 +44,20 @@
 
               ClassSubject bClassSubject = codeInspector.clazz(B.class);
               assertThat(bClassSubject, isPresent());
-              methodSubject = bClassSubject.method("void", "foo");
-              assertThat(methodSubject, isPackagePrivate());
+              if (enableHorizontalClassMerging) {
+                methodSubject = bClassSubject.method("void", "foo$bridge");
+                assertThat(methodSubject, isPackagePrivate());
+              }
 
               assertThat(
                   codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
 
               ClassSubject dClassSubject = codeInspector.clazz(D.class);
               assertThat(dClassSubject, isPresent());
-              methodSubject = dClassSubject.method("void", "foo");
-              assertThat(methodSubject, isPublic());
+              if (enableHorizontalClassMerging) {
+                methodSubject = dClassSubject.method("void", "foo$bridge");
+                assertThat(methodSubject, isPublic());
+              }
 
               ClassSubject eClassSubject = codeInspector.clazz(E.class);
               assertThat(eClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
index 9783627..185e090 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
@@ -60,7 +60,7 @@
                 assertThat(
                     otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print");
+                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
                 assertThat(printSubject, isPresent());
                 assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
index 109b0bc..a30149f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -67,7 +67,7 @@
                 assertThat(
                     otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print");
+                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
                 assertThat(printSubject, isPresent());
                 assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
index fea20ed..f6def29 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
@@ -60,7 +60,7 @@
                 assertThat(
                     otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print");
+                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
                 assertThat(printSubject, isPresent());
                 assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
index 605ff5e..2256e5d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -57,7 +57,7 @@
                 assertThat(
                     otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print");
+                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
                 assertThat(printSubject, isPresent());
                 assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
new file mode 100644
index 0000000..ff2f97b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MergedVirtualMethodStackTraceTest extends HorizontalClassMergingTestBase {
+  public MergedVirtualMethodStackTraceTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Program.Main.class)
+            .assertFailure()
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(Program.class)
+        .addKeepMainRule(Program.Main.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .addDontWarn(C.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class))
+        .run(parameters.getRuntime(), Program.Main.class)
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              assertThat(codeInspector.clazz(Program.A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(Program.B.class),
+                  notIf(isPresent(), enableHorizontalClassMerging));
+              if (enableHorizontalClassMerging) {
+                StackTrace expectedStackTraceWithMergedMethod =
+                    StackTrace.builder()
+                        .add(expectedStackTrace)
+                        // TODO(b/171294159): remove this hack that fixes the source code file
+                        .map(
+                            0,
+                            stackTraceLine ->
+                                stackTraceLine
+                                    .builderOf()
+                                    .setFileName(getClass().getSimpleName() + "$Program.java")
+                                    .build())
+                        .add(
+                            1,
+                            StackTraceLine.builder()
+                                .setClassName(Program.A.class.getTypeName())
+                                .setMethodName("foo$bridge")
+                                .setFileName("Program.java")
+                                .setFileName(getClass().getSimpleName() + ".java")
+                                .setLineNumber(stackTrace.get(1).lineNumber)
+                                .build())
+                        .build();
+                assertThat(stackTrace, isSame(expectedStackTraceWithMergedMethod));
+              }
+            });
+  }
+
+  public static class C {
+    public static void foo() {
+      System.out.println("foo c");
+    }
+  }
+
+  public static class Program {
+    @NeverClassInline
+    public static class A {
+      @NeverInline
+      public void foo() {
+        System.out.println("foo a");
+        try {
+          // Undefined reference, prevents inlining.
+          C.foo();
+        } catch (NoClassDefFoundError e) {
+        }
+      }
+    }
+
+    @NeverClassInline
+    public static class B {
+      @NeverInline
+      public void foo() {
+        try {
+          // Undefined reference, prevents inlining.
+          C.foo();
+        } catch (NoClassDefFoundError e) {
+        }
+        throw new RuntimeException();
+      }
+    }
+
+    public static class Main {
+      public static void main(String[] args) {
+        new A().foo();
+        new B().foo();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 0da2866..a64903b 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -16,6 +16,7 @@
 import com.google.common.base.Equivalence;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.hamcrest.Description;
@@ -58,6 +59,11 @@
       return this;
     }
 
+    public Builder map(int i, Function<StackTraceLine, StackTraceLine> map) {
+      stackTraceLines.set(i, map.apply(stackTraceLines.get(i)));
+      return this;
+    }
+
     public StackTrace build() {
       return new StackTrace(
           stackTraceLines,
@@ -123,6 +129,14 @@
       this.lineNumber = lineNumber;
     }
 
+    public Builder builderOf() {
+      return new Builder()
+          .setFileName(fileName)
+          .setClassName(className)
+          .setLineNumber(lineNumber)
+          .setMethodName(methodName);
+    }
+
     public static Builder builder() {
       return new Builder();
     }