Regression test for array clone in default or static interface methods

Bug: b/342802978
Change-Id: I25e4b4bc44e0286dbd3e2f91102726527af9c3c1
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index a8233fd..c43043c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1691,6 +1691,17 @@
             "bootstrap");
   }
 
+  public boolean isArrayClone(DexMethod method) {
+    return method.getHolderType().isArrayType()
+        && isObjectCloneWithoutHolderCheck(method.getProto(), method.getName());
+  }
+
+  public boolean isObjectCloneWithoutHolderCheck(DexProto proto, DexString name) {
+    return cloneMethodName.isIdenticalTo(name)
+        && proto.getParameters().isEmpty()
+        && objectType.isIdenticalTo(proto.getReturnType());
+  }
+
   public class ObjectMembers {
 
     /**
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
index 15c3b51..8b475ce 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -108,11 +108,10 @@
    * 10.7 of the Java Language Specification</a>. All invokations will have target java.lang.Object
    * except clone which has no target.
    */
-  @SuppressWarnings("ReferenceEquality")
   private MethodResolutionResult resolveMethodOnArray(
       DexType holder, DexProto methodProto, DexString methodName) {
     assert holder.isArrayType();
-    if (methodName == factory.cloneMethodName) {
+    if (factory.isObjectCloneWithoutHolderCheck(methodProto, methodName)) {
       return ArrayCloneMethodResult.INSTANCE;
     } else {
       return resolveMethodOnClass(factory.objectType, methodProto, methodName);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 75e7c28..fb052fb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -363,6 +363,12 @@
     }
 
     @Override
+    public void acceptInvokeObjectCloneOutliningMethod(
+        ProgramMethod method, ProgramMethod context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
     public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
@@ -625,7 +631,16 @@
     @Override
     public void acceptInvokeStaticInterfaceOutliningMethod(
         ProgramMethod method, ProgramMethod context) {
-      // Intentionally empty. The method will be hit by tracing if required.
+      // The method will be hit by tracing if required.
+      // Pin the synthetic so it is not inlined again.
+      additions.addMinimumSyntheticKeepInfo(method, Joiner::disallowInlining);
+    }
+
+    @Override
+    public void acceptInvokeObjectCloneOutliningMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // The method will be hit by tracing if required.
+      // Pin the synthetic so it is not inlined again.
       additions.addMinimumSyntheticKeepInfo(method, Joiner::disallowInlining);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 70db152..fc40466 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -131,6 +131,8 @@
                 lambdaDesugaring, stringConcatDesugaring, recordRewriter));
     if (interfaceMethodRewriter != null) {
       desugarings.add(interfaceMethodRewriter);
+    } else if (appView.options().canHaveArtArrayCloneFromInterfaceMethodBug()) {
+      desugarings.add(new OutlineArrayCloneFromInterfaceMethodDesugaring(appView));
     }
     desugaredLibraryAPIConverter =
         appView.typeRewriter.isRewriting()
@@ -186,6 +188,10 @@
     NonEmptyCfInstructionDesugaringCollection desugaringCollection =
         new NonEmptyCfInstructionDesugaringCollection(appView, apiLevelCompute);
     desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
+    if (appView.options().canHaveArtArrayCloneFromInterfaceMethodBug()) {
+      desugaringCollection.desugarings.add(
+          new OutlineArrayCloneFromInterfaceMethodDesugaring(appView));
+    }
     desugaringCollection.yieldingDesugarings.add(
         new UnrepresentableInDexInstructionRemover(appView));
     return desugaringCollection;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/OutlineArrayCloneFromInterfaceMethodDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/OutlineArrayCloneFromInterfaceMethodDesugaring.java
new file mode 100644
index 0000000..ce5fde6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/OutlineArrayCloneFromInterfaceMethodDesugaring.java
@@ -0,0 +1,90 @@
+// 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.ir.desugar;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+/** This outlines calls to array clone from within interface methods. See b/342802978 */
+public class OutlineArrayCloneFromInterfaceMethodDesugaring implements CfInstructionDesugaring {
+
+  private final AppView<?> appView;
+
+  public OutlineArrayCloneFromInterfaceMethodDesugaring(AppView<?> appView) {
+
+    this.appView = appView;
+  }
+
+  @Override
+  public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+    // This workaround only applies within default or static interface method.
+    if (!context.getHolder().isInterface()) {
+      return DesugarDescription.nothing();
+    }
+    DexEncodedMethod contextDefinition = context.getDefinition();
+    if (!contextDefinition.isStatic() && !contextDefinition.isNonPrivateVirtualMethod()) {
+      return DesugarDescription.nothing();
+    }
+    // The target method must be virtual clone on an array type.
+    if (!instruction.isInvokeVirtual()) {
+      return DesugarDescription.nothing();
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    if (!appView.dexItemFactory().isArrayClone(invoke.getMethod())) {
+      return DesugarDescription.nothing();
+    }
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (position, locals, stack, info, eventConsumer, ctx, pCtx, collection, factory) -> {
+              ProgramMethod newProgramMethod =
+                  appView
+                      .getSyntheticItems()
+                      .createMethod(
+                          kind -> kind.OBJECT_CLONE_OUTLINE,
+                          pCtx.createUniqueContext(),
+                          appView,
+                          methodBuilder ->
+                              methodBuilder
+                                  .setProto(
+                                      factory.createProto(factory.objectType, factory.objectType))
+                                  .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                                  .setCode(this::createCode));
+              eventConsumer.acceptInvokeObjectCloneOutliningMethod(newProgramMethod, ctx);
+              return createStaticInvoke(newProgramMethod);
+            })
+        .build();
+  }
+
+  private static List<CfInstruction> createStaticInvoke(ProgramMethod newProgramMethod) {
+    return Collections.singletonList(
+        new CfInvoke(Opcodes.INVOKESTATIC, newProgramMethod.getReference(), false));
+  }
+
+  private Code createCode(DexMethod method) {
+    return new CfCode(
+        method.getHolderType(),
+        1,
+        1,
+        ImmutableList.of(
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                Opcodes.INVOKEVIRTUAL, appView.dexItemFactory().objectMembers.clone, false),
+            new CfReturn(ValueType.OBJECT)));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
index 587dafa..8f537f9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
@@ -11,6 +11,8 @@
 
   void acceptInvokeStaticInterfaceOutliningMethod(ProgramMethod method, ProgramMethod context);
 
+  void acceptInvokeObjectCloneOutliningMethod(ProgramMethod method, ProgramMethod context);
+
   static EmptyInterfaceMethodDesugaringEventConsumer emptyInterfaceMethodDesugaringEventConsumer() {
     return EmptyInterfaceMethodDesugaringEventConsumer.INSTANCE;
   }
@@ -41,6 +43,12 @@
     }
 
     @Override
+    public void acceptInvokeObjectCloneOutliningMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptPrivateAsCompanionMethod(
         ProgramMethod method, ProgramMethod companionMethod) {
       // Intentionally empty.
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
index c9f2364..b556923 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -187,6 +187,12 @@
   }
 
   @Override
+  public void acceptInvokeObjectCloneOutliningMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+    parent.acceptInvokeObjectCloneOutliningMethod(method, context);
+  }
+
+  @Override
   public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
     addLambdaClassAndInstanceInitializersIfSynthesizingContextIsInProfile(lambdaClass, context);
     addLambdaFactoryMethodIfSynthesizingContextIsInProfile(lambdaClass, context);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 861e8e9..1ab00b6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -76,6 +76,7 @@
       generator.forSingleMethod("BackportWithForwarding");
   public final SyntheticKind STATIC_INTERFACE_CALL =
       generator.forSingleMethod("StaticInterfaceCall");
+  public final SyntheticKind OBJECT_CLONE_OUTLINE = generator.forSingleMethod("ObjectCloneOutline");
   public final SyntheticKind TO_STRING_IF_NOT_NULL =
       generator.forSingleMethodWithGlobalMerging("ToStringIfNotNull");
   public final SyntheticKind THROW_CCE_IF_NOT_NULL =
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ff09253..ab04762 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3066,6 +3066,12 @@
     return true;
   }
 
+  // Art 7 and up can fail access check for array clone calls from within interface methods.
+  // See b/342802978.
+  public boolean canHaveArtArrayCloneFromInterfaceMethodBug() {
+    return true;
+  }
+
   // The dalvik verifier will crash the program if there is a try catch block with an exception
   // type that does not exist.
   // We don't do anything special about this, except that we don't inline methods that have a
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodTest.java
new file mode 100644
index 0000000..23b27be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodTest.java
@@ -0,0 +1,93 @@
+// 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 static org.junit.Assume.assumeTrue;
+
+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
+@RunWith(Parameterized.class)
+public class ArrayCloneInDefaultInterfaceMethodTest 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 testDexNoDesugar() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(I.class, A.class, B.class, TestClass.class)
+        .setMinApi(parameters)
+        .disableDesugaring()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .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 strings.clone();
+    }
+  }
+
+  static class A implements I {}
+
+  static class B implements I {
+
+    @Override
+    public String[] myClone(String[] strings) {
+      return new String[] {"boo!"};
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // If the workaround is disabled and the code allocates "new B()" here in place of "null"
+      // then ART 14 no longer fails!
+      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/ArrayCloneInStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodTest.java
new file mode 100644
index 0000000..21f8ed2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodTest.java
@@ -0,0 +1,100 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+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
+@RunWith(Parameterized.class)
+public class ArrayCloneInStaticInterfaceMethodTest 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, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testArrayReturn() throws Exception {
+    // This just checks that the array clone must have exactly java.lang.Object as return type.
+    testForRuntime(parameters)
+        .addProgramClassFileData(
+            transformer(I.class)
+                .transformMethodInsnInMethod(
+                    "myClone",
+                    (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                      assertEquals("()Ljava/lang/Object;", descriptor);
+                      String newDescriptor = "()[Ljava/lang/Object;";
+                      visitor.visitMethodInsn(opcode, owner, name, newDescriptor, isInterface);
+                    })
+                .transform())
+        .addProgramClasses(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  @Test
+  public void testDexNoDesugar() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(I.class, TestClass.class)
+        .setMinApi(parameters)
+        .disableDesugaring()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(I.class)
+        .addProgramClasses(I.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) {
+      return strings.clone();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(I.myClone(args).length);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ObjectCloneInStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/resolution/ObjectCloneInStaticInterfaceMethodTest.java
new file mode 100644
index 0000000..64d0f14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/ObjectCloneInStaticInterfaceMethodTest.java
@@ -0,0 +1,106 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+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;
+import org.objectweb.asm.Opcodes;
+
+// Regression test to check Object.clone remains protected access (related to b/342802978).
+@RunWith(Parameterized.class)
+public class ObjectCloneInStaticInterfaceMethodTest 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)
+        .apply(this::addProgramInputs)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  private void addProgramInputs(TestBuilder<? extends SingleTestRunResult<?>, ?> builder)
+      throws Exception {
+    builder
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(
+            transformer(I.class)
+                .transformMethodInsnInMethod(
+                    "myClone",
+                    (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                      assertEquals("[Ljava/lang/String;", owner);
+                      String newOwner = binaryName(Object.class);
+                      visitor.visitTypeInsn(Opcodes.CHECKCAST, binaryName(Object.class));
+                      visitor.visitMethodInsn(
+                          Opcodes.INVOKESPECIAL, newOwner, name, descriptor, isInterface);
+                    })
+                .transform());
+  }
+
+  @Test
+  public void testDexNoDesugar() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .apply(this::addProgramInputs)
+        .setMinApi(parameters)
+        .disableDesugaring()
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(I.class)
+        .apply(this::addProgramInputs)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.isDexRuntime() || parameters.isCfRuntime(CfVm.JDK8),
+            this::checkOutput,
+            r -> r.assertFailureWithErrorThatThrows(VerifyError.class));
+  }
+
+  private void checkOutput(SingleTestRunResult<?> r) {
+    r.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+  }
+
+  interface I {
+
+    static String[] myClone(String[] strings) {
+      // Will be rewritten to invoke-special call to Object.clone().
+      return strings.clone();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(I.myClone(args).length);
+    }
+  }
+}