Refactor test of virtual call to private static method.
Bug: 139823850
Bug: 139918381
Change-Id: Ia4dd748ddc0eb92e6de7b769e4d8caa51e326796
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 34c05d1..6195ea0 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -120,7 +120,7 @@
ensureSameOutput(main, mergedApp, false, classes);
}
- protected static AndroidApp readClassesAndAsmDump(List<Class> classes, List<byte[]> asmClasses)
+ protected static AndroidApp readClassesAndAsmDump(List<Class<?>> classes, List<byte[]> asmClasses)
throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
for (Class clazz : classes) {
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index a90ae25..47aa023 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
@@ -14,10 +15,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.resolution.singletarget.Main;
-import com.android.tools.r8.resolution.singletarget.interfacedefault.A;
-import com.android.tools.r8.resolution.singletarget.interfacedefault.B;
-import com.android.tools.r8.resolution.singletarget.interfacedefault.C;
-import com.android.tools.r8.resolution.singletarget.interfacedefault.I;
import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
import com.android.tools.r8.resolution.singletarget.one.InterfaceWithDefault;
@@ -71,46 +68,44 @@
*/
public static AppInfoWithLiveness appInfo;
- public static List<Class> CLASSES = ImmutableList.of(
- InterfaceWithDefault.class,
- AbstractTopClass.class,
- AbstractSubClass.class,
- SubSubClassOne.class,
- SubSubClassTwo.class,
- SubSubClassThree.class,
- OtherAbstractTopClass.class,
- OtherAbstractSubClassOne.class,
- OtherAbstractSubClassTwo.class,
- OtherSubSubClassOne.class,
- OtherSubSubClassTwo.class,
- ThirdAbstractTopClass.class,
- ThirdSubClassOne.class,
- A.class,
- B.class,
- C.class,
- I.class,
- Main.class
- );
+ public static List<Class<?>> CLASSES =
+ ImmutableList.of(
+ InterfaceWithDefault.class,
+ AbstractTopClass.class,
+ AbstractSubClass.class,
+ SubSubClassOne.class,
+ SubSubClassTwo.class,
+ SubSubClassThree.class,
+ OtherAbstractTopClass.class,
+ OtherAbstractSubClassOne.class,
+ OtherAbstractSubClassTwo.class,
+ OtherSubSubClassOne.class,
+ OtherSubSubClassTwo.class,
+ ThirdAbstractTopClass.class,
+ ThirdSubClassOne.class,
+ Main.class);
public static List<byte[]> ASM_CLASSES = ImmutableList.of(
getBytesFromAsmClass(IrrelevantInterfaceWithDefaultDump::dump),
getBytesFromAsmClass(ThirdSubClassTwoDump::dump)
);
- public SingleTargetLookupTest(String methodName, Class invokeReceiver,
- Class singleTargetHolderOrNull, List<Class> allTargetHolders) {
+ public SingleTargetLookupTest(
+ String methodName,
+ Class invokeReceiver,
+ Class singleTargetHolderOrNull,
+ List<Class> allTargetHolders) {
this.methodName = methodName;
this.invokeReceiver = invokeReceiver;
this.singleTargetHolderOrNull = singleTargetHolderOrNull;
this.allTargetHolders = allTargetHolders;
}
- @BeforeClass
- public static void computeAppInfo() throws Exception {
+ public static AppInfoWithLiveness createAppInfoWithLiveness(AndroidApp app, Class<?> mainClass)
+ throws Exception {
// Run the tree shaker to compute an instance of AppInfoWithLiveness.
- Timing timing = new Timing(SingleTargetLookupTest.class.getCanonicalName());
+ Timing timing = new Timing();
InternalOptions options = new InternalOptions();
- AndroidApp app = readClassesAndAsmDump(CLASSES, ASM_CLASSES);
DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
AppView<? extends AppInfoWithSubtyping> appView =
AppView.createForR8(new AppInfoWithSubtyping(application), options);
@@ -119,23 +114,28 @@
ExecutorService executor = Executors.newSingleThreadExecutor();
RootSet rootSet =
new RootSetBuilder(
- appView, application, buildKeepRuleForClass(Main.class, application.dexItemFactory))
+ appView, application, buildKeepRuleForClass(mainClass, application.dexItemFactory))
.run(executor);
- appInfo =
- EnqueuerFactory.createForInitialTreeShaking(appView)
- .traceApplication(rootSet, ProguardClassFilter.empty(), executor, timing);
+ return EnqueuerFactory.createForInitialTreeShaking(appView)
+ .traceApplication(rootSet, ProguardClassFilter.empty(), executor, timing);
// We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
// due to liveness.
}
+ @BeforeClass
+ public static void computeAppInfo() throws Exception {
+ appInfo = createAppInfoWithLiveness(readClassesAndAsmDump(CLASSES, ASM_CLASSES), Main.class);
+ }
+
private static List<ProguardConfigurationRule> buildKeepRuleForClass(Class clazz,
DexItemFactory factory) {
Builder keepRuleBuilder = ProguardKeepRule.builder();
keepRuleBuilder.setSource("buildKeepRuleForClass " + clazz.getTypeName());
keepRuleBuilder.setType(ProguardKeepRuleType.KEEP);
keepRuleBuilder.setClassNames(
- ProguardClassNameList.singletonList(ProguardTypeMatcher.create(factory
- .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName())))));
+ ProguardClassNameList.singletonList(
+ ProguardTypeMatcher.create(
+ factory.createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))));
return Collections.singletonList(keepRuleBuilder.build());
}
@@ -204,24 +204,22 @@
singleTarget("instanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class),
singleTarget(
"otherInstanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class),
- // TODO(b/139823850): Should not include A#confusing. Rather, include default I#confusing.
- manyTargets("confusing", B.class, A.class, C.class)
});
}
- private static DexMethod buildMethod(Class clazz, String name) {
+ public static DexMethod buildMethod(Class clazz, String name, AppInfo appInfo) {
return appInfo
.dexItemFactory()
.createMethod(
- toType(clazz),
+ toType(clazz, appInfo),
appInfo.dexItemFactory().createProto(appInfo.dexItemFactory().voidType),
name);
}
- private static DexType toType(Class clazz) {
+ public static DexType toType(Class clazz, AppInfo appInfo) {
return appInfo
.dexItemFactory()
- .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName()));
+ .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()));
}
private final String methodName;
@@ -231,26 +229,29 @@
@Test
public void lookupSingleTarget() {
- DexMethod method = buildMethod(invokeReceiver, methodName);
- Assert.assertNotNull(appInfo.resolveMethod(toType(invokeReceiver), method).asResultOfResolve());
+ DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
+ Assert.assertNotNull(
+ appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve());
DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
if (singleTargetHolderOrNull == null) {
Assert.assertNull(singleVirtualTarget);
} else {
Assert.assertNotNull(singleVirtualTarget);
- Assert.assertEquals(toType(singleTargetHolderOrNull), singleVirtualTarget.method.holder);
+ Assert.assertEquals(
+ toType(singleTargetHolderOrNull, appInfo), singleVirtualTarget.method.holder);
}
}
@Test
public void lookupVirtualTargets() {
- DexMethod method = buildMethod(invokeReceiver, methodName);
- Assert.assertNotNull(appInfo.resolveMethod(toType(invokeReceiver), method).asResultOfResolve());
+ DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
+ Assert.assertNotNull(
+ appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve());
Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(method);
Set<DexType> targetHolders = targets.stream().map(m -> m.method.holder)
.collect(Collectors.toSet());
Assert.assertEquals(allTargetHolders.size(), targetHolders.size());
- Assert.assertTrue(allTargetHolders.stream().map(SingleTargetLookupTest::toType)
- .allMatch(targetHolders::contains));
+ Assert.assertTrue(
+ allTargetHolders.stream().map(t -> toType(t, appInfo)).allMatch(targetHolders::contains));
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
new file mode 100644
index 0000000..8b71aaf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2019, 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VirtualOverrideOfPrivateStaticMethodTest extends TestBase {
+
+ public interface I {
+ default void f() {}
+ }
+
+ public static class A {
+ private static void f() {}
+ }
+
+ public static class B extends A implements I {}
+
+ public static class C extends B {
+ public void f() {}
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B b = new C();
+ b.f();
+ }
+ }
+
+ public static List<Class<?>> CLASSES =
+ ImmutableList.of(A.class, B.class, C.class, I.class, Main.class);
+
+ private static AppInfoWithLiveness appInfo;
+
+ @BeforeClass
+ public static void computeAppInfo() throws Exception {
+ appInfo = SingleTargetLookupTest.createAppInfoWithLiveness(readClasses(CLASSES), Main.class);
+ }
+
+ private static DexMethod buildMethod(Class clazz, String name) {
+ return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+ }
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+ private final DexMethod methodOnA = buildMethod(A.class, "f");
+ private final DexMethod methodOnB = buildMethod(B.class, "f");
+ private final DexMethod methodOnC = buildMethod(C.class, "f");
+
+ public VirtualOverrideOfPrivateStaticMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void lookupSingleTarget() {
+ DexEncodedMethod resolved =
+ appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ assertEquals(methodOnA, resolved.method);
+ DexEncodedMethod singleVirtualTarget =
+ appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
+ Assert.assertNull(singleVirtualTarget);
+ }
+
+ @Test
+ public void lookupVirtualTargets() {
+ DexEncodedMethod resolved =
+ appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ assertEquals(methodOnA, resolved.method);
+ // This behavior is up for debate as the resolution target is A.f which is private static, thus
+ // the runtime behavior will be to throw a runtime exception. Thus it would be reasonable to
+ // return the empty set as no targets can actually be hit at runtime.
+ Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(methodOnB);
+ assertTrue("Expected " + methodOnA, targets.stream().anyMatch(m -> m.method == methodOnA));
+ assertTrue("Expected " + methodOnC, targets.stream().anyMatch(m -> m.method == methodOnC));
+ }
+
+ @Test
+ public void runTest() throws ExecutionException, CompilationFailedException, IOException {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(CLASSES)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ }
+ testForR8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ }
+
+ private String expectedRuntimeError() {
+ if (parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+ return "IncompatibleClassChangeError";
+ }
+ return "IllegalAccessError";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
new file mode 100644
index 0000000..a652ea7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -0,0 +1,210 @@
+// Copyright (c) 2019, 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest extends AsmTestBase {
+
+ public interface I {
+ default void f() {}
+ }
+
+ public static class Base {
+ private // Made public using ASM.
+ void f() {}
+ }
+
+ public static class A extends Base {
+ private static void f() {}
+ }
+
+ public static class B extends A implements I {}
+
+ public static class C extends B {
+ public void f() {
+ System.out.println("Called C.f");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B b = new C();
+ b.f();
+ }
+ }
+
+ public static class BaseDump implements Opcodes {
+
+ static String prefix(String suffix) {
+ return VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.class
+ .getTypeName()
+ .replace('.', '/')
+ + suffix;
+ }
+
+ public static byte[] dump() {
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+ cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, prefix("$Base"), null, "java/lang/Object", null);
+ cw.visitSource("VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java", null);
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ // Changed ACC_PRIVATE to ACC_PUBLIC
+ mv = cw.visitMethod(ACC_PUBLIC, "f", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+ return cw.toByteArray();
+ }
+ }
+
+ public static List<Class<?>> CLASSES =
+ ImmutableList.of(A.class, B.class, C.class, I.class, Main.class);
+
+ public static List<byte[]> DUMPS = ImmutableList.of(BaseDump.dump());
+
+ private static AppInfoWithLiveness appInfo;
+
+ @BeforeClass
+ public static void computeAppInfo() throws Exception {
+ appInfo =
+ SingleTargetLookupTest.createAppInfoWithLiveness(
+ readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+ }
+
+ private static DexMethod buildMethod(Class clazz, String name) {
+ return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+ }
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+ private final DexMethod methodOnA = buildMethod(A.class, "f");
+ private final DexMethod methodOnB = buildMethod(B.class, "f");
+ private final DexMethod methodOnC = buildMethod(C.class, "f");
+
+ public VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void lookupSingleTarget() {
+ DexEncodedMethod resolved =
+ appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ assertEquals(methodOnA, resolved.method);
+ DexEncodedMethod singleVirtualTarget =
+ appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
+ Assert.assertNull(singleVirtualTarget);
+ }
+
+ @Test
+ public void lookupVirtualTargets() {
+ DexEncodedMethod resolved =
+ appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ assertEquals(methodOnA, resolved.method);
+ // See comment in VirtualOverrideOfPrivateStaticMethodTest.lookupVirtualTargets().
+ Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(methodOnB);
+ assertTrue("Expected " + methodOnA, targets.stream().anyMatch(m -> m.method == methodOnA));
+ assertTrue("Expected " + methodOnC, targets.stream().anyMatch(m -> m.method == methodOnC));
+ }
+
+ @Test
+ public void runJvmAndD8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DUMPS)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ } else {
+ D8TestRunResult runResult =
+ testForD8()
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DUMPS)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class);
+ if (expectedToIncorrectlyRun(parameters.getRuntime())) {
+ // Do to incorrect resolution, some Art VMs will resolve to Base.f (ignoring A.f) and thus
+ // virtual dispatch to C.f.
+ runResult.assertSuccessWithOutputLines("Called C.f");
+ } else {
+ runResult.assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ }
+ }
+ }
+
+ @Test
+ public void runR8() throws Exception {
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DUMPS)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class);
+ if (expectedToIncorrectlyRun(parameters.getRuntime())) {
+ // Do to incorrect resolution, some Art VMs will resolve to Base.f (ignoring A.f) and thus
+ // virtual dispatch to C.f.
+ runResult.assertSuccessWithOutputLines("Called C.f");
+ } else {
+ runResult.assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ }
+ }
+
+ private boolean expectedToIncorrectlyRun(TestRuntime runtime) {
+ return runtime.isDex()
+ && runtime.asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+ && runtime.asDex().getVm().isOlderThanOrEqual(DexVm.ART_7_0_0_HOST);
+ }
+
+ private String expectedRuntimeError() {
+ if (parameters.isDexRuntime()
+ && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+ return "IncompatibleClassChangeError";
+ }
+ return "IllegalAccessError";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
new file mode 100644
index 0000000..57608d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -0,0 +1,238 @@
+// Copyright (c) 2019, 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class VirtualOverrideOfStaticMethodWithVirtualParentTest extends AsmTestBase {
+
+ public interface I {
+ default void f() {}
+ }
+
+ public static class Base {
+ private // Made public using ASM.
+ void f() {}
+ }
+
+ public static class A extends Base {
+ private // Made public using ASM.
+ static void f() {}
+ }
+
+ public static class B extends A implements I {}
+
+ public static class C extends B {
+ public void f() {
+ System.out.println("Called C.f");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B b = new C();
+ b.f();
+ }
+ }
+
+ public static class BaseDump implements Opcodes {
+
+ static String prefix(String suffix) {
+ return VirtualOverrideOfStaticMethodWithVirtualParentTest.class
+ .getTypeName()
+ .replace('.', '/')
+ + suffix;
+ }
+
+ public static byte[] dump() {
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, prefix("$Base"), null, "java/lang/Object", null);
+ MethodVisitor mv;
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ // Changed ACC_PRIVATE to ACC_PUBLIC
+ mv = cw.visitMethod(ACC_PUBLIC, "f", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+ return cw.toByteArray();
+ }
+ }
+
+ public static class ADump implements Opcodes {
+
+ static String prefix(String suffix) {
+ return VirtualOverrideOfStaticMethodWithVirtualParentTest.class
+ .getTypeName()
+ .replace('.', '/')
+ + suffix;
+ }
+
+ public static byte[] dump() {
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, prefix("$A"), null, prefix("$Base"), null);
+ MethodVisitor mv;
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, prefix("$Base"), "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ // Changed ACC_PRIVATE to ACC_PUBLIC
+ mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "f", "()V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+ return cw.toByteArray();
+ }
+ }
+
+ public static List<Class<?>> CLASSES = ImmutableList.of(B.class, C.class, I.class, Main.class);
+
+ public static List<byte[]> DUMPS = ImmutableList.of(BaseDump.dump(), ADump.dump());
+
+ private static AppInfoWithLiveness appInfo;
+
+ @BeforeClass
+ public static void computeAppInfo() throws Exception {
+ appInfo =
+ SingleTargetLookupTest.createAppInfoWithLiveness(
+ readClassesAndAsmDump(CLASSES, DUMPS), Main.class);
+ }
+
+ private static DexMethod buildMethod(Class clazz, String name) {
+ return SingleTargetLookupTest.buildMethod(clazz, name, appInfo);
+ }
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+ private final DexMethod methodOnA = buildMethod(A.class, "f");
+ private final DexMethod methodOnB = buildMethod(B.class, "f");
+ private final DexMethod methodOnC = buildMethod(C.class, "f");
+
+ public VirtualOverrideOfStaticMethodWithVirtualParentTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void lookupSingleTarget() {
+ DexEncodedMethod resolved =
+ appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ assertEquals(methodOnA, resolved.method);
+ DexEncodedMethod singleVirtualTarget =
+ appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
+ Assert.assertNull(singleVirtualTarget);
+ }
+
+ @Test
+ public void lookupVirtualTargets() {
+ DexEncodedMethod resolved =
+ appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ assertEquals(methodOnA, resolved.method);
+ // See comment in VirtualOverrideOfPrivateStaticMethodTest.lookupVirtualTargets().
+ Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(methodOnB);
+ assertTrue("Expected " + methodOnA, targets.stream().anyMatch(m -> m.method == methodOnA));
+ assertTrue("Expected " + methodOnC, targets.stream().anyMatch(m -> m.method == methodOnC));
+ }
+
+ @Test
+ public void testJvmAndD8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DUMPS)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ } else {
+ D8TestRunResult runResult =
+ testForD8()
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DUMPS)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class);
+ if (expectedToIncorrectlyRun(parameters.getRuntime())) {
+ // Do to incorrect resolution, some Art VMs will resolve to Base.f (ignoring A.f) and thus
+ // virtual dispatch to C.f.
+ runResult.assertSuccessWithOutputLines("Called C.f");
+ } else {
+ runResult.assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ }
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DUMPS)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class);
+ // TODO(b/139918381): The result of R8 contains invalid code, thus most VMs fail class loading.
+ // Some Art VMs fail too, but not in a consistent manner, so not encoding that here.
+ if (parameters.isCfRuntime()) {
+ runResult.assertFailureWithErrorThatMatches(containsString("ClassFormatError"));
+ }
+ }
+
+ private boolean expectedToIncorrectlyRun(TestRuntime runtime) {
+ return runtime.isDex()
+ && runtime.asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+ && runtime.asDex().getVm().isOlderThanOrEqual(DexVm.ART_7_0_0_HOST);
+ }
+
+ private static String expectedRuntimeError() {
+ return "IncompatibleClassChangeError";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/A.java b/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/A.java
deleted file mode 100644
index 4914380..0000000
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/A.java
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2019, 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.singletarget.interfacedefault;
-
-public class A {
- private static void confusing() {
- System.out.println("A::confusing()");
- }
-}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/B.java b/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/B.java
deleted file mode 100644
index b575db5..0000000
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/B.java
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2019, 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.singletarget.interfacedefault;
-
-public class B extends A implements I {
- @Override
- public void confusing(int i) {
- System.out.println("B::confusing(" + i + ")");
- }
-}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/C.java b/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/C.java
deleted file mode 100644
index 2940ea3..0000000
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/C.java
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2019, 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.singletarget.interfacedefault;
-
-public class C extends B {
- @Override
- public void confusing() {
- System.out.println("C::confusing()");
- }
-}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/I.java b/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/I.java
deleted file mode 100644
index 04ff6ba..0000000
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/interfacedefault/I.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2019, 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.singletarget.interfacedefault;
-
-public interface I {
- void confusing(int i);
-
- default void confusing() {
- System.out.println("I::confusing()");
- confusing(0);
- }
-}