Merge "Compute local info at exceptional blocks based on its predecessor."
diff --git a/src/main/java/com/android/tools/r8/dex/DexFile.java b/src/main/java/com/android/tools/r8/dex/DexFile.java
index a2c65d3..3441346 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFile.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFile.java
@@ -62,13 +62,13 @@
int version;
switch (versionByte) {
case '8':
- version = 38;
+ version = Constants.ANDROID_O_DEX_VERSION;
break;
case '7':
- version = 37;
+ version = Constants.ANDROID_N_DEX_VERSION;
break;
case '5':
- version = 35;
+ version = Constants.ANDROID_PRE_N_DEX_VERSION;
break;
default:
throw new CompilationError("Dex file has invalid version number: " + name);
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index f336a3d..f309670 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -174,6 +174,35 @@
return isFull(transaction.getNumberOfMethods(), transaction.getNumberOfFields(), MAX_ENTRIES);
}
+ void throwIfFull(boolean multiDexEnabled) {
+ if (!isFull()) {
+ return;
+ }
+ StringBuilder messageBuilder = new StringBuilder();
+ // General message: Cannot fit.
+ messageBuilder.append("Cannot fit requested classes in ");
+ messageBuilder.append(multiDexEnabled ? "the main-" : "a single ");
+ messageBuilder.append("dex file.\n");
+ // Suggest supplying the main-dex list or explicitly mention that main-dex list is too large.
+ if (multiDexEnabled) {
+ messageBuilder.append("The list of classes for the main-dex list is too large.\n");
+ } else {
+ messageBuilder.append("Try supplying a main-dex list.\n");
+ }
+ // Show the numbers of methods and/or fields that exceed the limit.
+ if (transaction.getNumberOfMethods() > MAX_ENTRIES) {
+ messageBuilder.append("# methods: ");
+ messageBuilder.append(transaction.getNumberOfMethods());
+ messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
+ }
+ if (transaction.getNumberOfFields() > MAX_ENTRIES) {
+ messageBuilder.append("# fields: ");
+ messageBuilder.append(transaction.getNumberOfFields());
+ messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
+ }
+ throw new CompilationError(messageBuilder.toString());
+ }
+
private boolean isFilledEnough(FillStrategy fillStrategy) {
return isFull(
transaction.getNumberOfMethods(),
@@ -202,7 +231,7 @@
protected final ApplicationWriter writer;
protected final Map<Integer, VirtualFile> nameToFileMap = new HashMap<>();
- public Distributor(ApplicationWriter writer) {
+ Distributor(ApplicationWriter writer) {
this.application = writer.application;
this.writer = writer;
}
@@ -212,7 +241,7 @@
public static class FilePerClassDistributor extends Distributor {
- public FilePerClassDistributor(ApplicationWriter writer) {
+ FilePerClassDistributor(ApplicationWriter writer) {
super(writer);
}
@@ -232,7 +261,7 @@
protected Set<DexProgramClass> classes;
protected Map<DexProgramClass, String> originalNames;
- public DistributorBase(ApplicationWriter writer) {
+ DistributorBase(ApplicationWriter writer) {
super(writer);
classes = Sets.newHashSet(application.classes());
@@ -247,9 +276,7 @@
if (clazz != null && clazz.isProgramClass()) {
DexProgramClass programClass = (DexProgramClass) clazz;
mainDexFile.addClass(programClass);
- if (mainDexFile.isFull()) {
- throw new CompilationError("Cannot fit requested classes in main-dex file.");
- }
+ mainDexFile.throwIfFull(true);
classes.remove(programClass);
} else {
System.out.println(
@@ -298,7 +325,7 @@
public static class FillFilesDistributor extends DistributorBase {
private final FillStrategy fillStrategy;
- public FillFilesDistributor(ApplicationWriter writer, boolean minimalMainDex) {
+ FillFilesDistributor(ApplicationWriter writer, boolean minimalMainDex) {
super(writer);
this.fillStrategy = minimalMainDex ? FillStrategy.MINIMAL_MAIN_DEX : FillStrategy.FILL_MAX;
}
@@ -326,7 +353,7 @@
}
public static class MonoDexDistributor extends DistributorBase {
- public MonoDexDistributor(ApplicationWriter writer) {
+ MonoDexDistributor(ApplicationWriter writer) {
super(writer);
}
@@ -337,9 +364,7 @@
for (DexProgramClass programClass : classes) {
mainDexFile.addClass(programClass);
- if (mainDexFile.isFull()) {
- throw new CompilationError("Cannot fit all classes in a single dex file.");
- }
+ mainDexFile.throwIfFull(false);
}
mainDexFile.commitTransaction();
return nameToFileMap;
@@ -350,7 +375,7 @@
private final PackageDistribution packageDistribution;
private final ExecutorService executorService;
- public PackageMapDistributor(
+ PackageMapDistributor(
ApplicationWriter writer,
PackageDistribution packageDistribution,
ExecutorService executorService) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3619b9c..3601ee9 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -226,17 +226,44 @@
((DexEncodedMethod) dexItem).getCode() != null;
}
- private void checkIfMethodIsAmbiguous(DexItem previousResult, DexItem newResult) {
+ /** Returns if <code>interface1</code> is a super interface of <code>interface2</code> */
+ private boolean isSuperInterfaceOf(DexType interface1, DexType interface2) {
+ assert definitionFor(interface1).isInterface();
+ DexClass holder = definitionFor(interface2);
+ assert holder.isInterface();
+ for (DexType iface : holder.interfaces.values) {
+ if (iface == interface1 || isSuperInterfaceOf(interface1, iface)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private <S extends DexItem> S resolveAmbiguousResult(S previousResult, S newResult) {
+ // For default methods return the item found lowest in the interface hierarchy. Different
+ // implementations can come from different paths in a diamond.
+ // See §9.4.1 in The Java® Language Specification, Java SE 8 Edition.
if (previousResult != null
&& previousResult != newResult
&& isDefaultMethod(previousResult)
&& isDefaultMethod(newResult)) {
+ DexEncodedMethod previousMethod = (DexEncodedMethod) previousResult;
+ DexEncodedMethod newMethod = (DexEncodedMethod) newResult;
+ if (isSuperInterfaceOf(previousMethod.method.getHolder(), newMethod.method.getHolder())) {
+ return newResult;
+ }
+ if (isSuperInterfaceOf(newMethod.method.getHolder(), previousMethod.method.getHolder())) {
+ return previousResult;
+ }
throw new CompilationError("Duplicate default methods named "
+ previousResult.toSourceString()
+ " are inherited from the types "
- + ((DexEncodedMethod) previousResult).method.holder.getName()
+ + previousMethod.method.holder.getName()
+ " and "
- + ((DexEncodedMethod) newResult).method.holder.getName());
+ + newMethod.method.holder.getName());
+ } else {
+ // Return the first item found for everything except default methods.
+ return previousResult != null ? previousResult : newResult;
}
}
@@ -256,21 +283,13 @@
for (DexType iface : holder.interfaces.values) {
S localResult = lookupTargetAlongSuperAndInterfaceChain(iface, desc, lookup);
if (localResult != null) {
- checkIfMethodIsAmbiguous(result, localResult);
- // Return the first item found, we only continue to detect ambiguous method call.
- if (result == null) {
- result = localResult;
- }
+ result = resolveAmbiguousResult(result, localResult);
}
}
if (holder.superType != null) {
S localResult = lookupTargetAlongInterfaceChain(holder.superType, desc, lookup);
if (localResult != null) {
- checkIfMethodIsAmbiguous(result, localResult);
- // Return the first item found, we only continue to detect ambiguous method call.
- if (result == null) {
- result = localResult;
- }
+ result = resolveAmbiguousResult(result, localResult);
}
}
return result;
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 0b8d876..065d3df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -41,11 +41,13 @@
return field;
}
+ abstract DexEncodedField lookupTarget(DexType type, AppInfo appInfo);
+
@Override
public Constraint inliningConstraint(AppInfo info, DexType holder) {
// Resolve the field if possible and decide whether the instruction can inlined.
DexType fieldHolder = field.getHolder();
- DexEncodedField target = info.lookupInstanceTarget(fieldHolder, field);
+ DexEncodedField target = lookupTarget(fieldHolder, info);
DexClass fieldClass = info.definitionFor(fieldHolder);
if ((target != null) && (fieldClass != null) && !fieldClass.isLibraryClass()) {
DexAccessFlags flags = target.accessFlags;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index f38614f..1d9e853 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -13,7 +13,10 @@
import com.android.tools.r8.code.IgetWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
public class InstanceGet extends FieldInstruction {
@@ -95,6 +98,11 @@
}
@Override
+ DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+ return appInfo.lookupInstanceTarget(type, field);
+ }
+
+ @Override
public boolean isInstanceGet() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 7ec8eaf..2642de1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -13,7 +13,10 @@
import com.android.tools.r8.code.IputWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
import java.util.List;
@@ -96,6 +99,11 @@
}
@Override
+ DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+ return appInfo.lookupInstanceTarget(type, field);
+ }
+
+ @Override
public boolean isInstancePut() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index ad340c2..bb79d5e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -12,7 +12,10 @@
import com.android.tools.r8.code.SgetWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
public class StaticGet extends FieldInstruction {
@@ -91,6 +94,11 @@
}
@Override
+ DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+ return appInfo.lookupStaticTarget(type, field);
+ }
+
+ @Override
public String toString() {
return super.toString() + "; field: " + field.toSourceString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index e8a1fed..6736d7e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -12,7 +12,10 @@
import com.android.tools.r8.code.SputWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
public class StaticPut extends FieldInstruction {
@@ -93,6 +96,11 @@
}
@Override
+ DexEncodedField lookupTarget(DexType type, AppInfo appInfo) {
+ return appInfo.lookupStaticTarget(type, field);
+ }
+
+ @Override
public String toString() {
return super.toString() + "; field: " + field.toSourceString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 4ede5a8..a2b3ba2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -302,9 +302,9 @@
continue;
}
// Ensure the container is compatible with the target.
- if (!forceInline
- && !result.target.isPublicInlining()
- && (method.method.getHolder() != result.target.method.getHolder())) {
+ if (!forceInline
+ && !result.target.isPublicInlining()
+ && (method.method.getHolder() != result.target.method.getHolder())) {
continue;
}
DexType downcast = null;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 3a77a52..6f1a9b2 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1732,14 +1732,8 @@
for (Phi phi : successor.getPhis()) {
LiveIntervals toIntervals = phi.getLiveIntervals().getSplitCovering(toInstruction);
Value operand = phi.getOperand(predIndex);
- LiveIntervals fromIntervals = operand.getLiveIntervals();
- if (operand.isPhi() && operand != phi && successor.getPhis().contains(operand)) {
- // If the input to this phi is another phi in this block we want the value after
- // merging which is the value for that phi at the from instruction.
- fromIntervals = fromIntervals.getSplitCovering(fromInstruction);
- } else {
- fromIntervals = fromIntervals.getSplitCovering(fromInstruction);
- }
+ LiveIntervals fromIntervals =
+ operand.getLiveIntervals().getSplitCovering(fromInstruction);
if (fromIntervals != toIntervals && !toIntervals.isArgumentInterval()) {
assert block.getSuccessors().size() == 1;
spillMoves.addPhiMove(fromInstruction - 1, toIntervals, fromIntervals);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 69fdd65..a5e2309 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -150,8 +150,15 @@
@Test
public void cannotFitBothIntoMainDex() throws Throwable {
- thrown.expect(CompilationError.class);
- verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
+ try {
+ verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
+ fail("Expect to fail, for there are too many classes for the main-dex list.");
+ } catch (CompilationError e) {
+ // Make sure {@link MonoDexDistributor} was _not_ used.
+ assertFalse(e.getMessage().contains("single dex file"));
+ // Make sure what exceeds the limit is the number of methods.
+ assertTrue(e.getMessage().contains("# methods"));
+ }
}
@Test
@@ -183,8 +190,15 @@
@Test
public void cannotFitAllIntoMainDex() throws Throwable {
- thrown.expect(CompilationError.class);
- verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
+ try {
+ verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
+ fail("Expect to fail, for there are too many classes for the main-dex list.");
+ } catch (CompilationError e) {
+ // Make sure {@link MonoDexDistributor} was _not_ used.
+ assertFalse(e.getMessage().contains("single dex file"));
+ // Make sure what exceeds the limit is the number of methods.
+ assertTrue(e.getMessage().contains("# methods"));
+ }
}
@Test
@@ -334,7 +348,10 @@
MANY_CLASSES, Constants.ANDROID_K_API, false, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
fail("Expect to fail, for there are many classes while multidex is not enabled.");
} catch (CompilationError e) {
- assertTrue(e.getMessage().contains("Cannot fit all classes in a single dex file."));
+ // Make sure {@link MonoDexDistributor} was used.
+ assertTrue(e.getMessage().contains("single dex file"));
+ // Make sure what exceeds the limit is the number of methods.
+ assertTrue(e.getMessage().contains("# methods"));
}
}
@@ -476,8 +493,8 @@
}
DexApplication application = builder.build();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
- ApplicationWriter writer =
- new ApplicationWriter(application, appInfo, options, null, NamingLens.getIdentityLens(), null);
+ ApplicationWriter writer = new ApplicationWriter(
+ application, appInfo, options, null, NamingLens.getIdentityLens(), null);
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return writer.write(null, executor);
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
new file mode 100644
index 0000000..70d4efc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2017, 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.regress.b63935662;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApp;
+import java.nio.file.Path;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Regress63935662 extends TestBase {
+
+ void run(AndroidApp app, Class mainClass) throws Exception {
+ if (!ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1)) {
+ return;
+ }
+ Path proguardConfig = writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(app)
+ .addProguardConfigurationFiles(proguardConfig)
+ .setMinApiLevel(Constants.ANDROID_N_API)
+ .build();
+ String resultFromJava = runOnJava(mainClass);
+ app = ToolHelper.runR8(command);
+ String resultFromArt = runOnArt(app, mainClass);
+ Assert.assertEquals(resultFromJava, resultFromArt);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Class mainClass = TestClass.class;
+ AndroidApp app = readClasses(
+ TestClass.Top.class, TestClass.Left.class, TestClass.Right.class, TestClass.Bottom.class,
+ TestClass.X1.class, TestClass.X2.class, TestClass.X3.class, TestClass.X4.class, TestClass.X5.class,
+ mainClass);
+ run(app, mainClass);
+ }
+
+ @Test
+ public void test2() throws Exception {
+ Class mainClass = TestFromBug.class;
+ AndroidApp app = readClasses(
+ TestFromBug.Map.class, TestFromBug.AbstractMap.class,
+ TestFromBug.ConcurrentMap.class, TestFromBug.ConcurrentHashMap.class,
+ mainClass);
+ run(app, mainClass);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
new file mode 100644
index 0000000..878fcff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2017, 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.regress.b63935662;
+
+public class TestClass {
+
+ interface Top {
+ default String name() { return "unnamed"; }
+ }
+
+ interface Left extends Top {
+ default String name() { return getClass().getName(); }
+ }
+
+ interface Right extends Top {
+ /* No override of default String name() */
+ }
+
+ interface Bottom extends Left, Right {}
+
+ static class X1 implements Bottom {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X2 implements Left, Right {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X3 implements Right, Left {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X4 implements Left, Right, Top {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X5 implements Right, Left, Top {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ public static void main(String[] args) {
+ new X1().test();
+ new X2().test();
+ new X3().test();
+ new X4().test();
+ new X5().test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java b/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java
new file mode 100644
index 0000000..9d3efc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, 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.regress.b63935662;
+
+import java.util.function.BiConsumer;
+
+public class TestFromBug {
+
+ public interface Map<K, V> {
+ default void forEach(BiConsumer<? super K, ? super V> action) {
+ System.out.println("Map.forEach");
+ }
+ }
+
+ public interface ConcurrentMap<K, V> extends Map<K,V> {
+ @Override
+ default void forEach(BiConsumer<? super K, ? super V> action) {
+ System.out.println("ConcurrentMap.forEach");
+ }
+ }
+
+ public static abstract class AbstractMap<K,V> implements Map<K, V> {}
+ public static class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V> {}
+
+ public static void main(String[] args) {
+ new ConcurrentHashMap<String, String>().forEach(null);
+ }
+}
\ No newline at end of file