Regression test for retracing synthetic backport methods
Bug: b/326196974
Change-Id: I6c9e2822f8df8d45cc02255b9ee16bc4651cd0c9
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceBackportMethodClass.java b/src/test/java/com/android/tools/r8/retrace/RetraceBackportMethodClass.java
new file mode 100644
index 0000000..8cda2b4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceBackportMethodClass.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2024, 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.retrace;
+
+public class RetraceBackportMethodClass {
+
+ public static class A {
+
+ public void run(int divisor) {
+ System.out.println(Math.floorDiv(42, divisor));
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ (System.nanoTime() > 0 ? new A() : null).run(System.nanoTime() > 0 ? 0 : 1);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceBackportMethodTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceBackportMethodTest.java
new file mode 100644
index 0000000..c8d0015
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceBackportMethodTest.java
@@ -0,0 +1,233 @@
+// Copyright (c) 2024, 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.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceBackportMethodTest extends TestBase {
+
+ static final Class<?> CLASS = RetraceBackportMethodClass.class;
+ static final Class<?> CLASS_A = RetraceBackportMethodClass.A.class;
+ static final Class<?> CLASS_MAIN = RetraceBackportMethodClass.Main.class;
+
+ static List<Class<?>> getInputClasses() {
+ return ImmutableList.of(CLASS, CLASS_A, CLASS_MAIN);
+ }
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ private boolean isCfVmWithModulePrefix() {
+ return parameters.isCfRuntime() && !parameters.isCfRuntime(CfVm.JDK8);
+ }
+
+ private boolean isApiWithFloorDivIntSupport() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ parameters.assumeJvmTestParameters();
+ boolean includeMathFrame = true;
+ boolean includeExternalSyntheticFrame = false;
+ boolean includeJvmModule = isCfVmWithModulePrefix();
+ boolean doNotCheckLines = true;
+ testForJvm(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), CLASS_MAIN)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(
+ stackTrace ->
+ checkExpectedStackTrace(
+ stackTrace,
+ includeMathFrame,
+ includeExternalSyntheticFrame,
+ includeJvmModule,
+ doNotCheckLines));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ boolean includeMathFrame = isApiWithFloorDivIntSupport();
+ boolean includeExternalSyntheticFrame = !includeMathFrame;
+ boolean includeJvmModule = includeMathFrame && isCfVmWithModulePrefix();
+ boolean doNotCheckLines = includeMathFrame;
+ testForD8(parameters.getBackend())
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), CLASS_MAIN)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(
+ stackTrace ->
+ checkExpectedStackTrace(
+ stackTrace,
+ includeMathFrame,
+ includeExternalSyntheticFrame,
+ includeJvmModule,
+ doNotCheckLines));
+ }
+
+ @Test
+ public void testD8DebugRetrace() throws Exception {
+ // TODO(b/326196974): Should always have the frame.
+ boolean includeMathFrame = isApiWithFloorDivIntSupport();
+ boolean includeExternalSyntheticFrame = false;
+ boolean includeJvmModule = isApiWithFloorDivIntSupport() && isCfVmWithModulePrefix();
+ boolean doNotCheckLines = isApiWithFloorDivIntSupport();
+ testForD8(parameters.getBackend())
+ .debug()
+ .internalEnableMappingOutput()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), CLASS_MAIN)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(
+ stackTrace ->
+ checkExpectedStackTrace(
+ stackTrace,
+ includeMathFrame,
+ includeExternalSyntheticFrame,
+ includeJvmModule,
+ doNotCheckLines));
+ }
+
+ @Test
+ public void testD8ReleaseRetrace() throws Exception {
+ // TODO(b/326196974): Should always have the frame.
+ boolean includeMathFrame = isApiWithFloorDivIntSupport();
+ boolean includeExternalSyntheticFrame = false;
+ boolean includeJvmModule = isApiWithFloorDivIntSupport() && isCfVmWithModulePrefix();
+ boolean doNotCheckLines = isApiWithFloorDivIntSupport();
+ testForD8(parameters.getBackend())
+ .release()
+ .internalEnableMappingOutput()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), CLASS_MAIN)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(
+ stackTrace ->
+ checkExpectedStackTrace(
+ stackTrace,
+ includeMathFrame,
+ includeExternalSyntheticFrame,
+ includeJvmModule,
+ doNotCheckLines));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ // TODO(b/326196974): Should always have the frame.
+ boolean includeMathFrame = parameters.isCfRuntime() || isApiWithFloorDivIntSupport();
+ ;
+ boolean includeExternalSyntheticFrame = false;
+ boolean includeJvmModule = isCfVmWithModulePrefix();
+ boolean doNotCheckLines = parameters.isCfRuntime() || isApiWithFloorDivIntSupport();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getInputClasses())
+ .addKeepMainRule(CLASS_MAIN)
+ .addKeepAttributeSourceFile()
+ .addKeepAttributeLineNumberTable()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), CLASS_MAIN)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(
+ stackTrace ->
+ checkExpectedStackTrace(
+ stackTrace,
+ includeMathFrame,
+ includeExternalSyntheticFrame,
+ includeJvmModule,
+ doNotCheckLines));
+ }
+
+ private void checkRunResult(SingleTestRunResult<?> runResult) {
+ runResult.assertFailureWithErrorThatThrows(ArithmeticException.class);
+ }
+
+ private void checkExpectedStackTrace(
+ StackTrace stackTrace,
+ boolean includeMathFrame,
+ boolean includeExternalSyntheticFrame,
+ boolean includeJvmModule,
+ boolean doNotCheckLines) {
+ StackTrace.Builder builder = StackTrace.builder();
+ if (includeMathFrame) {
+ assertFalse(includeExternalSyntheticFrame);
+ ClassReference objects = Reference.classFromClass(Math.class);
+ String mathFrameFormat = (includeJvmModule ? "java.base/" : "") + objects.getTypeName();
+ builder.add(
+ StackTraceLine.builder()
+ .setFileName("Math.java")
+ .setClassName(mathFrameFormat)
+ .setMethodName("floorDiv")
+ .setLineNumber(-1)
+ .build());
+ }
+ if (includeExternalSyntheticFrame) {
+ assertFalse(includeMathFrame);
+ builder.add(
+ StackTraceLine.builder()
+ .setFileName(SyntheticItemsTestUtils.syntheticFileNameD8())
+ .setClassName(
+ SyntheticItemsTestUtils.syntheticBackportClass(CLASS_A, 0).getTypeName())
+ .setMethodName(SyntheticItemsTestUtils.syntheticMethodName())
+ .setLineNumber(parameters.isCfRuntime() ? -1 : 0)
+ .build());
+ }
+ String fileName = ToolHelper.getSourceFileForTestClass(CLASS).getFileName().toString();
+ builder
+ .add(
+ StackTraceLine.builder()
+ .setFileName(fileName)
+ .setClassName(typeName(CLASS_A))
+ .setMethodName("run")
+ .setLineNumber(12)
+ .build())
+ .add(
+ StackTraceLine.builder()
+ .setFileName(fileName)
+ .setClassName(typeName(CLASS_MAIN))
+ .setMethodName("main")
+ .setLineNumber(19)
+ .build());
+ if (doNotCheckLines) {
+ // We can't check the line numbers when using the native support of Objects.requireNonNull.
+ assertThat(stackTrace, isSameExceptForLineNumbers(builder.build()));
+ } else {
+ assertThat(stackTrace, isSame(builder.build()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index a5e5f19..bb789fc 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -35,6 +35,10 @@
// id/descriptor content is safe.
private static final SyntheticNaming naming = new SyntheticNaming();
+ public static String syntheticFileNameD8() {
+ return "D8$$SyntheticClass";
+ }
+
public static String syntheticMethodName() {
return SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_NAME;
}