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