Regression test for R8 creating code with ART array clone issue
Bug: b/342802978
Change-Id: I33c2384549d5b623d238400d626d61222a423f3b
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 337d90d..bc22ae8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -480,6 +481,11 @@
return null;
}
+ if (isInliningBlockedDueToArrayClone(context, singleTarget, appView)) {
+ whyAreYouNotInliningReporter.reportUnsafeDueToArrayCloneCall();
+ return null;
+ }
+
// Make sure constructor inlining is legal.
if (singleTarget.getDefinition().isInstanceInitializer()
&& !canInlineInstanceInitializer(
@@ -624,6 +630,25 @@
return false;
}
+ public static boolean isInliningBlockedDueToArrayClone(
+ ProgramMethod context, ProgramMethod singleTarget, AppView<?> appView) {
+ if (!appView.options().canHaveArtArrayCloneFromInterfaceMethodBug()) {
+ return false;
+ }
+ if (!context.getHolder().isInterface()) {
+ return false;
+ }
+ return singleTarget.registerCodeReferencesWithResult(
+ new DefaultUseRegistryWithResult<>(appView, context, false) {
+ @Override
+ public void registerInvokeVirtual(DexMethod method) {
+ if (appView.dexItemFactory().isArrayClone(method)) {
+ setResult(true);
+ }
+ }
+ });
+ }
+
@Override
@SuppressWarnings("ReferenceEquality")
public boolean canInlineInstanceInitializer(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 6359da4..403719e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -57,6 +57,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -1340,6 +1341,9 @@
// Using -allowaccessmodification mitigates this.
return false;
}
+ if (DefaultInliningOracle.isInliningBlockedDueToArrayClone(method, singleTarget, appView)) {
+ return false;
+ }
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 1f116b4..cb7523f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -137,4 +137,7 @@
public boolean unsetReasonHasBeenReportedFlag() {
return true;
}
+
+ @Override
+ public void reportUnsafeDueToArrayCloneCall() {}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index b333817..ca2d508 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -127,4 +127,6 @@
int numberOfMonitorEnterValuesAfterInlining, int threshold);
public abstract boolean unsetReasonHasBeenReportedFlag();
+
+ public abstract void reportUnsafeDueToArrayCloneCall();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 23391b7..8a04363 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -297,4 +297,10 @@
reasonHasBeenReported = false;
return true;
}
+
+ @Override
+ public void reportUnsafeDueToArrayCloneCall() {
+ report(
+ "would lead to unsupported resolution of array clone() from within an interface method.");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java
new file mode 100644
index 0000000..a81e419
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java
@@ -0,0 +1,80 @@
+// 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.resolution;
+
+import com.android.tools.r8.NeverInline;
+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 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;
+
+// Regression test for b/342802978 after R8 class inlining.
+@RunWith(Parameterized.class)
+public class ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return TestParameters.builder()
+ .withAllRuntimes()
+ .withApiLevel(apiLevelWithDefaultInterfaceMethodsSupport())
+ .build();
+ }
+
+ @Parameter public TestParameters parameters;
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, A.class, B.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassAndMembersRules(I.class)
+ .addProgramClasses(I.class, A.class, B.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkOutput);
+ }
+
+ private void checkOutput(SingleTestRunResult<?> r) {
+ r.assertSuccessWithOutputLines("0");
+ }
+
+ interface I {
+
+ default String[] myClone(String[] strings) {
+ return new B().myClone(strings);
+ }
+ }
+
+ static class A implements I {}
+
+ static class B {
+
+ @NeverInline
+ public String[] myClone(String[] strings) {
+ return strings.clone();
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ I i = System.nanoTime() > 0 ? new A() : null;
+ System.out.println(i.myClone(args).length);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java
new file mode 100644
index 0000000..edae17d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java
@@ -0,0 +1,86 @@
+// 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.resolution;
+
+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 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;
+
+// Regression test for b/342802978, but with R8 giving rise to the issue after inlining.
+@RunWith(Parameterized.class)
+public class ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return TestParameters.builder()
+ .withAllRuntimes()
+ .withApiLevel(apiLevelWithDefaultInterfaceMethodsSupport())
+ .build();
+ }
+
+ @Parameter public TestParameters parameters;
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, A.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("0");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addKeepMainRule(TestClass.class)
+ .addProgramClasses(I.class, A.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkOutput);
+ }
+
+ private void checkOutput(SingleTestRunResult<?> r) {
+ r.assertSuccessWithOutputLines("0");
+ }
+
+ interface I {
+
+ static String[] myClone(String[] strings) {
+ try {
+ return A.inlinedClone(strings);
+ } catch (RuntimeException e) {
+ // Extra code to avoid simple inlining.
+ System.out.println("Unexpected exception: " + e);
+ throw e;
+ }
+ }
+ }
+
+ static class A {
+
+ public static String[] inlinedClone(String[] strings) {
+ return strings.clone();
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ int count = 0;
+ // Repeated calls to avoid single-caller inlining of the interface method.
+ count += I.myClone(args).length;
+ count += I.myClone(args).length;
+ count += I.myClone(args).length;
+ count += I.myClone(args).length;
+ count += I.myClone(args).length;
+ System.out.println(count);
+ }
+ }
+}