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