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