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