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();
}