Add reproduction of b/192310793

This also includes a read/write interleaving checked MethodCollection
that can be used for testing.

Bug: 192310793
Change-Id: Ic735328969a11ba68c3f49b85e8a9791629b7a1a
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 0074673..92b5b2f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -109,7 +109,7 @@
     this.type = type;
     setStaticFields(staticFields);
     setInstanceFields(instanceFields);
-    this.methodCollection = new MethodCollection(this, directMethods, virtualMethods);
+    this.methodCollection = MethodCollection.create(this, directMethods, virtualMethods);
     this.nestHost = nestHost;
     this.nestMembers = nestMembers;
     assert nestMembers != null;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index d445f9f..5d1de0a 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -2,6 +2,7 @@
 
 import static com.google.common.base.Predicates.alwaysTrue;
 
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ArrayList;
@@ -23,7 +24,7 @@
   private MethodCollectionBacking backing;
   private DexEncodedMethod cachedClassInitializer = DexEncodedMethod.SENTINEL;
 
-  public MethodCollection(
+  MethodCollection(
       DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
     this.holder = holder;
     if (directMethods.length + virtualMethods.length > ARRAY_BACKING_THRESHOLD) {
@@ -35,6 +36,14 @@
     backing.setVirtualMethods(virtualMethods);
   }
 
+  public static MethodCollection create(
+      DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
+    if (InternalOptions.USE_METHOD_COLLECTION_CONCURRENCY_CHECKED) {
+      return new MethodCollectionConcurrencyChecked(holder, directMethods, virtualMethods);
+    }
+    return new MethodCollection(holder, directMethods, virtualMethods);
+  }
+
   private void resetCaches() {
     resetDirectMethodCaches();
     resetVirtualMethodCaches();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
new file mode 100644
index 0000000..66be012
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
@@ -0,0 +1,338 @@
+// Copyright (c) 2021, 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.graph;
+
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MethodCollectionConcurrencyChecked extends MethodCollection {
+  private AtomicInteger readCount = new AtomicInteger();
+  private AtomicInteger writeCount = new AtomicInteger();
+
+  MethodCollectionConcurrencyChecked(
+      DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
+    super(holder, directMethods, virtualMethods);
+  }
+
+  private boolean assertReadEntry() {
+    assert writeCount.get() == 0;
+    assert readCount.incrementAndGet() >= 1;
+    return true;
+  }
+
+  private boolean assertReadExit() {
+    assert readCount.decrementAndGet() >= 0;
+    assert writeCount.get() == 0;
+    return true;
+  }
+
+  private boolean assertWriteEntry() {
+    assert readCount.get() == 0;
+    assert writeCount.incrementAndGet() == 1;
+    return true;
+  }
+
+  private boolean assertWriteExit() {
+    assert writeCount.decrementAndGet() == 0;
+    assert readCount.get() == 0;
+    return true;
+  }
+
+  @Override
+  public boolean hasDirectMethods(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    boolean result = super.getDirectMethod(predicate) != null;
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public boolean hasVirtualMethods(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    boolean result = super.getVirtualMethod(predicate) != null;
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public int numberOfDirectMethods() {
+    assert assertReadEntry();
+    int result = super.numberOfDirectMethods();
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public int numberOfVirtualMethods() {
+    assert assertReadEntry();
+    int result = super.numberOfVirtualMethods();
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public int size() {
+    assert assertReadEntry();
+    int result = super.size();
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
+    assert assertReadEntry();
+    TraversalContinuation result = super.traverse(fn);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public void forEachMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    assert assertReadEntry();
+    super.forEachMethodMatching(predicate, consumer);
+    assert assertReadExit();
+  }
+
+  @Override
+  public void forEachDirectMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    assert assertReadEntry();
+    super.forEachDirectMethodMatching(predicate, consumer);
+    assert assertReadExit();
+  }
+
+  @Override
+  public void forEachVirtualMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    assert assertReadEntry();
+    super.forEachVirtualMethodMatching(predicate, consumer);
+    assert assertReadExit();
+  }
+
+  @Override
+  public Iterable<DexEncodedMethod> methods() {
+    // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+    return super.methods();
+  }
+
+  @Override
+  public Iterable<DexEncodedMethod> directMethods() {
+    // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+    return super.directMethods();
+  }
+
+  @Override
+  public Iterable<DexEncodedMethod> virtualMethods() {
+    // TODO(sgjesse): Maybe wrap in an iterator that checks a modification counter.
+    return super.virtualMethods();
+  }
+
+  @Override
+  public DexEncodedMethod getMethod(DexMethod method) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getMethod(method);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getMethod(predicate);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getDirectMethod(DexMethod method) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getDirectMethod(method);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getDirectMethod(predicate);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getVirtualMethod(DexMethod method) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getVirtualMethod(method);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
+    assert assertReadEntry();
+    DexEncodedMethod result = super.getVirtualMethod(predicate);
+    assert assertReadExit();
+    return result;
+  }
+
+  @Override
+  public void addMethod(DexEncodedMethod method) {
+    assert assertWriteEntry();
+    super.addMethod(method);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void addVirtualMethod(DexEncodedMethod virtualMethod) {
+    assert assertWriteEntry();
+    super.addVirtualMethod(virtualMethod);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void addDirectMethod(DexEncodedMethod directMethod) {
+    assert assertWriteEntry();
+    super.addDirectMethod(directMethod);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.replaceDirectMethod(method, replacement);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public DexEncodedMethod replaceVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.replaceVirtualMethod(method, replacement);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceDirectMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceVirtualMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceAllDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceAllDirectMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void replaceAllVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    super.replaceAllVirtualMethods(replacement);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.replaceDirectMethodWithVirtualMethod(method, replacement);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public void addDirectMethods(Collection<DexEncodedMethod> methods) {
+    assert assertWriteEntry();
+    super.addDirectMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void clearDirectMethods() {
+    assert assertWriteEntry();
+    super.clearDirectMethods();
+    assert assertWriteExit();
+  }
+
+  @Override
+  public DexEncodedMethod removeMethod(DexMethod method) {
+    assert assertWriteEntry();
+    DexEncodedMethod result = super.removeMethod(method);
+    assert assertWriteExit();
+    return result;
+  }
+
+  @Override
+  public void removeMethods(Set<DexEncodedMethod> methods) {
+    assert assertWriteEntry();
+    super.removeMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void setDirectMethods(DexEncodedMethod[] methods) {
+    assert assertWriteEntry();
+    super.setDirectMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+    assert assertWriteEntry();
+    super.addVirtualMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void clearVirtualMethods() {
+    assert assertWriteEntry();
+    super.clearVirtualMethods();
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void setVirtualMethods(DexEncodedMethod[] methods) {
+    assert assertWriteEntry();
+    super.setVirtualMethods(methods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    assert assertWriteEntry();
+    super.virtualizeMethods(privateInstanceMethods);
+    assert assertWriteExit();
+  }
+
+  @Override
+  public void useSortedBacking() {
+    assert assertWriteEntry();
+    super.useSortedBacking();
+    assert assertWriteExit();
+  }
+}
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 0e01a16..3401ced 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -96,6 +96,9 @@
   // This makes life easier when running R8 in a debugger.
   public static final boolean DETERMINISTIC_DEBUGGING = false;
 
+  // Use a MethodCollection where most interleavings between reading and mutating is caught.
+  public static final boolean USE_METHOD_COLLECTION_CONCURRENCY_CHECKED = false;
+
   public enum LineNumberOptimization {
     OFF,
     ON
diff --git a/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java b/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java
new file mode 100644
index 0000000..380127b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/ConcurrencyTest.java
@@ -0,0 +1,371 @@
+// Copyright (c) 2021, 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.desugar;
+
+import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.objectweb.asm.Opcodes;
+
+// Test for reproducing b/192310793.
+@RunWith(Parameterized.class)
+public class ConcurrencyTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntimesEndingAtExcluding(JDK11)
+            .withDexRuntimes()
+            .withApiLevel(AndroidApiLevel.B)
+            .build());
+  }
+
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class);
+  }
+
+  public Collection<byte[]> getTransformedClasses() throws Exception {
+    ClassFileTransformer transformer =
+        withNest(Host.class)
+            .setVersion(CfVersion.V1_8)
+            .transformMethodInsnInMethod(
+                "callPrivate",
+                ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+                  continuation.visitMethodInsn(
+                      name.equals("hello") ? Opcodes.INVOKEVIRTUAL : opcode,
+                      owner,
+                      name,
+                      descriptor,
+                      isInterface);
+                }));
+    for (String s : new String[] {"a", "b", "c", "d", "e"}) {
+      for (int i = 0; i < 10; i++) {
+        transformer.setPrivate(Host.class.getDeclaredMethod(s + "0" + i));
+      }
+    }
+
+    return ImmutableList.of(
+        transformer.transform(),
+        withNest(A.class).transform(),
+        withNest(B.class).transform(),
+        withNest(C.class).transform(),
+        withNest(D.class).transform(),
+        withNest(E.class).transform());
+  }
+
+  private ClassFileTransformer withNest(Class<?> clazz) throws Exception {
+    return transformer(clazz).setNest(Host.class, A.class, B.class, C.class, D.class, E.class);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    try {
+      testForD8(parameters.getBackend())
+          .addProgramClasses(getClasses())
+          .addProgramClassFileData(getTransformedClasses())
+          .compile();
+    } catch (CompilationFailedException e) {
+      if (e.getCause() instanceof ArrayIndexOutOfBoundsException) {
+        // TODO(b/192310793): This should not happen.
+        return;
+      }
+      throw e;
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.getBackend().isDex());
+
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(getClasses())
+          .addProgramClassFileData(getTransformedClasses())
+          .setMinApi(parameters.getApiLevel())
+          .addKeepAllClassesRule()
+          .compile();
+    } catch (CompilationFailedException e) {
+      if (e.getCause() instanceof AssertionError
+          && e.getCause()
+              .getStackTrace()[0]
+              .getClassName()
+              .equals(
+                  "com.android.tools.r8.ir.desugar.NonEmptyCfInstructionDesugaringCollection")) {
+        // TODO(b/192446461): This should not happen.
+        return;
+      }
+      throw e;
+    }
+  }
+
+  static class Host {
+    /* will be private */ void a00() {}
+    /* will be private */ void a01() {}
+    /* will be private */ void a02() {}
+    /* will be private */ void a03() {}
+    /* will be private */ void a04() {}
+    /* will be private */ void a05() {}
+    /* will be private */ void a06() {}
+    /* will be private */ void a07() {}
+    /* will be private */ void a08() {}
+    /* will be private */ void a09() {}
+
+    /* will be private */ void b00() {}
+    /* will be private */ void b01() {}
+    /* will be private */ void b02() {}
+    /* will be private */ void b03() {}
+    /* will be private */ void b04() {}
+    /* will be private */ void b05() {}
+    /* will be private */ void b06() {}
+    /* will be private */ void b07() {}
+    /* will be private */ void b08() {}
+    /* will be private */ void b09() {}
+
+    /* will be private */ void c00() {}
+    /* will be private */ void c01() {}
+    /* will be private */ void c02() {}
+    /* will be private */ void c03() {}
+    /* will be private */ void c04() {}
+    /* will be private */ void c05() {}
+    /* will be private */ void c06() {}
+    /* will be private */ void c07() {}
+    /* will be private */ void c08() {}
+    /* will be private */ void c09() {}
+
+    /* will be private */ void d00() {}
+    /* will be private */ void d01() {}
+    /* will be private */ void d02() {}
+    /* will be private */ void d03() {}
+    /* will be private */ void d04() {}
+    /* will be private */ void d05() {}
+    /* will be private */ void d06() {}
+    /* will be private */ void d07() {}
+    /* will be private */ void d08() {}
+    /* will be private */ void d09() {}
+
+    /* will be private */ void e00() {}
+    /* will be private */ void e01() {}
+    /* will be private */ void e02() {}
+    /* will be private */ void e03() {}
+    /* will be private */ void e04() {}
+    /* will be private */ void e05() {}
+    /* will be private */ void e06() {}
+    /* will be private */ void e07() {}
+    /* will be private */ void e08() {}
+    /* will be private */ void e09() {}
+
+    private void hello() {}
+
+    public static void callPrivate() {
+      // The private method "hello" is called with invokevirtual.
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+      new Host().hello();
+    }
+  }
+
+  static class A {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().a00();
+      new Host().a01();
+      new Host().a02();
+      new Host().a03();
+      new Host().a04();
+      new Host().a05();
+      new Host().a06();
+      new Host().a07();
+      new Host().a08();
+      new Host().a09();
+    }
+  }
+
+  static class B {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().b00();
+      new Host().b01();
+      new Host().b02();
+      new Host().b03();
+      new Host().b04();
+      new Host().b05();
+      new Host().b06();
+      new Host().b07();
+      new Host().b08();
+      new Host().b09();
+    }
+  }
+
+  static class C {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().c00();
+      new Host().c01();
+      new Host().c02();
+      new Host().c03();
+      new Host().c04();
+      new Host().c05();
+      new Host().c06();
+      new Host().c07();
+      new Host().c08();
+      new Host().c09();
+    }
+  }
+
+  static class D {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().d00();
+      new Host().d01();
+      new Host().d02();
+      new Host().d03();
+      new Host().d04();
+      new Host().d05();
+      new Host().d06();
+      new Host().d07();
+      new Host().d08();
+      new Host().d09();
+    }
+  }
+
+  static class E {
+    public void foo() {
+      // Will be virtual invoke to private methods.
+      new Host().e00();
+      new Host().e01();
+      new Host().e02();
+      new Host().e03();
+      new Host().e04();
+      new Host().e05();
+      new Host().e06();
+      new Host().e07();
+      new Host().e08();
+      new Host().e09();
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new A().foo();
+      new B().foo();
+      new C().foo();
+      new D().foo();
+      new E().foo();
+      Host.callPrivate();
+    }
+  }
+}