Version 2.1.53
Cherry-pick: Desugared lib: Fix keep rules for empty classes
CL: https://r8-review.googlesource.com/c/r8/+/52660
Cherry-pick: Desugared lib: Fix getGenericTypes
CL: https://r8-review.googlesource.com/c/r8/+/52640
Cherry-pick: Desugared lib: fix overrides
CL: https://r8-review.googlesource.com/c/r8/+/52620
Bug: 160905482
Bug: 160909126
Bug: 162437880
Change-Id: I4c7008366f14e9d8bc6a4b37dd1596f5e62e5a32
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7a6cb48..7874bb9 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.1.52";
+ public static final String LABEL = "2.1.53";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 9eb7caa..f4e85a8 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
@@ -36,6 +37,8 @@
abstract void recordClassAllAccesses(DexType type);
+ abstract void recordHierarchyOf(DexProgramClass clazz);
+
abstract boolean isNop();
abstract void generateKeepRules(InternalOptions options);
@@ -116,6 +119,14 @@
}
}
+ @Override
+ void recordHierarchyOf(DexProgramClass clazz) {
+ recordClassAllAccesses(clazz.superType);
+ for (DexType itf : clazz.interfaces.values) {
+ recordClassAllAccesses(itf);
+ }
+ }
+
private void keepClass(DexType type) {
DexType baseType = type.lookupBaseType(options.itemFactory);
toKeep.putIfAbsent(baseType, new KeepStruct());
@@ -193,6 +204,9 @@
void recordClassAllAccesses(DexType type) {}
@Override
+ void recordHierarchyOf(DexProgramClass clazz) {}
+
+ @Override
boolean isNop() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 0047bf8..8d1eac7 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -467,6 +467,8 @@
}
private void writeClassDefItem(DexProgramClass clazz) {
+ desugaredLibraryCodeToKeep.recordHierarchyOf(clazz);
+
dest.putInt(mapping.getOffsetFor(clazz.type));
dest.putInt(clazz.accessFlags.getAsDexAccessFlags());
dest.putInt(
@@ -659,10 +661,6 @@
private void writeClassData(DexProgramClass clazz) {
assert clazz.hasMethodsOrFields();
- desugaredLibraryCodeToKeep.recordClassAllAccesses(clazz.superType);
- for (DexType itf : clazz.interfaces.values) {
- desugaredLibraryCodeToKeep.recordClassAllAccesses(itf);
- }
mixedSectionOffsets.setOffsetFor(clazz, dest.position());
dest.putUleb128(clazz.staticFields().size());
dest.putUleb128(clazz.instanceFields().size());
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 900712b..a211636 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -226,8 +226,8 @@
}
@Override
- public void collectIndexedItems(IndexedItemCollection indexedItems,
- DexMethod method, int instructionOffset) {
+ public void collectIndexedItems(
+ IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
if (indexedItems.addClass(this)) {
type.collectIndexedItems(indexedItems, method, instructionOffset);
if (superType != null) {
@@ -256,8 +256,8 @@
}
}
- private static <T extends DexItem> void synchronizedCollectAll(IndexedItemCollection collection,
- T[] items) {
+ private static <T extends DexItem> void synchronizedCollectAll(
+ IndexedItemCollection collection, T[] items) {
synchronized (items) {
collectAll(collection, items);
}
@@ -443,6 +443,50 @@
methodCollection.addDirectMethod(directMethod);
}
+ public void addExtraInterfaces(List<DexType> extraInterfaces, DexItemFactory factory) {
+ if (extraInterfaces.isEmpty()) {
+ return;
+ }
+ addExtraInterfacesToInterfacesArray(extraInterfaces);
+ addExtraInterfacesToSignatureAnnotationIfPresent(extraInterfaces, factory);
+ }
+
+ private void addExtraInterfacesToInterfacesArray(List<DexType> extraInterfaces) {
+ DexType[] newInterfaces =
+ Arrays.copyOf(interfaces.values, interfaces.size() + extraInterfaces.size());
+ for (int i = interfaces.size(); i < newInterfaces.length; i++) {
+ newInterfaces[i] = extraInterfaces.get(i - interfaces.size());
+ }
+ interfaces = new DexTypeList(newInterfaces);
+ }
+
+ private void addExtraInterfacesToSignatureAnnotationIfPresent(
+ List<DexType> extraInterfaces, DexItemFactory factory) {
+ // We need to introduce in the dalvik.annotation.Signature annotation the extra interfaces.
+ // At this point we cheat and pretend the extraInterfaces simply don't use any generic types.
+ DexAnnotation[] annotations = annotations().annotations;
+ for (int i = 0; i < annotations.length; i++) {
+ DexAnnotation annotation = annotations[i];
+ if (DexAnnotation.isSignatureAnnotation(annotation, factory)) {
+ DexAnnotation[] rewrittenAnnotations = annotations.clone();
+ rewrittenAnnotations[i] = rewriteSignatureAnnotation(annotation, extraInterfaces, factory);
+ setAnnotations(new DexAnnotationSet(rewrittenAnnotations));
+ // There is at most one signature annotation, so we can return here.
+ return;
+ }
+ }
+ }
+
+ private DexAnnotation rewriteSignatureAnnotation(
+ DexAnnotation annotation, List<DexType> extraInterfaces, DexItemFactory factory) {
+ String signature = DexAnnotation.getSignature(annotation);
+ StringBuilder newSignatureBuilder = new StringBuilder(signature);
+ for (DexType extraInterface : extraInterfaces) {
+ newSignatureBuilder.append(extraInterface.descriptor.toString());
+ }
+ return DexAnnotation.createSignatureAnnotation(newSignatureBuilder.toString(), factory);
+ }
+
@Override
public DexProgramClass get() {
return this;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 4737d39..643c9fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -320,8 +320,10 @@
return;
}
- // If target is a non-interface library class it may be an emulated interface.
- if (!libraryHolder.isInterface()) {
+ // If target is a non-interface library class it may be an emulated interface,
+ // except on a rewritten type, where L8 has already dealt with the desugaring.
+ if (!libraryHolder.isInterface()
+ && !appView.rewritePrefix.hasRewrittenType(libraryHolder.type, appView)) {
// Here we use step-3 of resolution to find a maximally specific default interface method.
DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(libraryHolder, method);
if (result != null && rewriter.isEmulatedInterface(result.getHolder().type)) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 316f299..408b246 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ResolutionResult;
@@ -33,7 +32,6 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
@@ -324,14 +322,11 @@
// methods.
// We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
// applies up to 24.
- for (DexMethod dexMethod : methods) {
- DexType[] newInterfaces =
- Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
- newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(dexMethod);
- clazz.interfaces = new DexTypeList(newInterfaces);
- DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
- if (dexEncodedMethod == null) {
- DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+ for (DexMethod method : methods) {
+ clazz.addExtraInterfaces(
+ Collections.singletonList(dispatchInterfaceTypeFor(method)), appView.dexItemFactory());
+ if (clazz.lookupVirtualMethod(method) == null) {
+ DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
clazz.addVirtualMethod(newMethod);
newForwardingMethodsConsumer.accept(newMethod);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 98078c5..b963160 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -920,7 +920,7 @@
private void duplicateEmulatedInterfaces() {
// All classes implementing an emulated interface now implements the interface and the
// emulated one, as well as hidden overrides, for correct emulated dispatch.
- for (DexClass clazz : appView.appInfo().classes()) {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
if (clazz.type == appView.dexItemFactory().objectType) {
continue;
}
@@ -940,17 +940,11 @@
}
}
// Remove duplicates.
- extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
- }
- if (!extraInterfaces.isEmpty()) {
- DexType[] newInterfaces =
- Arrays.copyOf(
- clazz.interfaces.values, clazz.interfaces.size() + extraInterfaces.size());
- for (int i = clazz.interfaces.size(); i < newInterfaces.length; i++) {
- newInterfaces[i] = extraInterfaces.get(i - clazz.interfaces.size());
+ if (extraInterfaces.size() > 1) {
+ extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
}
- clazz.interfaces = new DexTypeList(newInterfaces);
}
+ clazz.addExtraInterfaces(extraInterfaces, appView.dexItemFactory());
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
index f13eddb..ae709f3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -18,6 +19,9 @@
@RunWith(Parameterized.class)
public class ConcurrentHashMapSubclassTest extends DesugaredLibraryTestBase {
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("1.0", "2.0", "10.0", "1.0", "2.0", "10.0", "1.0", "2.0", "10.0");
+
private final TestParameters parameters;
private final boolean shrinkDesugaredLibrary;
@@ -46,7 +50,7 @@
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
@@ -64,7 +68,7 @@
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+ .assertSuccessWithOutput(EXPECTED_RESULT);
}
@SuppressWarnings("unchecked")
@@ -78,24 +82,33 @@
static void itfType() {
Map map = new NullableConcurrentHashMap<Integer, Double>();
map.put(1, 1.0);
+ map.putIfAbsent(2, 2.0);
+ map.putIfAbsent(2, 3.0);
map.putAll(example());
System.out.println(map.get(1));
+ System.out.println(map.get(2));
System.out.println(map.get(10));
}
static void classType() {
ConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
map.put(1, 1.0);
+ map.putIfAbsent(2, 2.0);
+ map.putIfAbsent(2, 3.0);
map.putAll(example());
System.out.println(map.get(1));
+ System.out.println(map.get(2));
System.out.println(map.get(10));
}
static void directType() {
NullableConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
map.put(1, 1.0);
+ map.putIfAbsent(2, 2.0);
+ map.putIfAbsent(2, 3.0);
map.putAll(example());
System.out.println(map.get(1));
+ System.out.println(map.get(2));
System.out.println(map.get(10));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
new file mode 100644
index 0000000..0eab37e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibraryEmptySubclassInterfaceTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, 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.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+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 LibraryEmptySubclassInterfaceTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public LibraryEmptySubclassInterfaceTest(
+ boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines(getResult());
+ assertExpectedKeepRules(keepRuleConsumer);
+ }
+
+ private void assertExpectedKeepRules(KeepRuleConsumer keepRuleConsumer) {
+ if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+ return;
+ }
+ String keepRules = keepRuleConsumer.get();
+ assertThat(keepRules, containsString("-keep class j$.util.Map"));
+ assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentHashMap"));
+ assertThat(keepRules, containsString("-keep class j$.util.concurrent.ConcurrentMap"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(Backend.DEX)
+ .addInnerClasses(LibraryEmptySubclassInterfaceTest.class)
+ .addKeepMainRule(Executor.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines(getResult());
+ assertExpectedKeepRules(keepRuleConsumer);
+ }
+
+ private String getResult() {
+ return requiresEmulatedInterfaceCoreLibDesugaring(parameters)
+ ? "class j$.util.concurrent.ConcurrentHashMap"
+ : "class java.util.concurrent.ConcurrentHashMap";
+ }
+
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ static class Executor {
+
+ public static void main(String[] args) {
+ System.out.println(NullableConcurrentHashMap.class.getSuperclass());
+ }
+ }
+
+ static class NullableConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
+ NullableConcurrentHashMap() {
+ super();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
new file mode 100644
index 0000000..6e300ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LibrarySubclassInterfaceTest.java
@@ -0,0 +1,1012 @@
+// Copyright (c) 2020, 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.desugaredlibrary;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import dalvik.system.PathClassLoader;
+import java.sql.SQLDataException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+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 LibrarySubclassInterfaceTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public LibrarySubclassInterfaceTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCustomCollectionD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ String stdOut =
+ testForD8()
+ .addInnerClasses(LibrarySubclassInterfaceTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertValidInterfaces(stdOut);
+ }
+
+ @Test
+ public void testCustomCollectionR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ String stdOut =
+ testForR8(Backend.DEX)
+ .addInnerClasses(LibrarySubclassInterfaceTest.class)
+ .addKeepMainRule(Executor.class)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertValidInterfaces(stdOut);
+ }
+
+ private void assertValidInterfaces(String stdOut) {
+ // The value of getGenericInterfaces has to be the value of getInterfaces with generic types.
+ // Here are two examples:
+ // - class A implements I {}
+ // getInterfaces -> [interface I]
+ // getGenericInterfaces -> [interface I]
+ // - class B<E> implements J<E> {}
+ // getInterfaces -> [interface J]
+ // getGenericInterfaces -> [J<E>]
+ // Both arrays have to be of the same size and each class has to be present in the same order.
+ String[] lines = stdOut.split("\n");
+ for (int i = 0; i < lines.length; i += 4) {
+ String className = lines[i];
+ String[] interfaces1 = lines[i + 1].split("(, com|, interface|, j)");
+ String[] interfaces2 = lines[i + 2].split("(, com|, interface|, j)");
+ assertEquals(
+ "Invalid number of interfaces in "
+ + className
+ + "\n "
+ + Arrays.toString(interfaces1)
+ + "\n "
+ + Arrays.toString(interfaces2),
+ interfaces1.length,
+ interfaces2.length);
+ // Ignore the empty list of interface case.
+ if (!interfaces1[0].equals("[]")) {
+ for (int j = 0; j < interfaces1.length; j++) {
+ String interfaceName = interfaces1[j].substring("interface ".length()).trim();
+ while (interfaceName.charAt(interfaceName.length() - 1) == ']') {
+ interfaceName = interfaceName.substring(0, interfaceName.length() - 2).trim();
+ }
+ assertTrue(
+ "Invalid interface in " + className + "\n " + interfaces1[j] + "\n " + interfaces2[j],
+ interfaces2[j].contains(interfaceName));
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ static class Executor {
+
+ // The output of the test is, in stdOut, composed of 4 lines entries:
+ // line 1: class name
+ // line 2: getInterfaces() for the class
+ // line 3: getGenericInterfaces() for the class
+ // line 4: empty.
+ public static void main(String[] args) {
+ mapTest();
+ collectionTest();
+ collectionMapTest();
+ sqlDateTest();
+ }
+
+ private static void mapTest() {
+ System.out.println(NullableConcurrentHashMapExtendDifferentLetters.class);
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapExtendDifferentLetters.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(
+ NullableConcurrentHashMapExtendDifferentLetters.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapExtend.class);
+ System.out.println(Arrays.toString(NullableConcurrentHashMapExtend.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapExtend.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapExtendZ.class);
+ System.out.println(Arrays.toString(NullableConcurrentHashMapExtendZ.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapExtendZ.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapImplement.class);
+ System.out.println(Arrays.toString(NullableConcurrentHashMapImplement.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapImplement.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableConcurrentHashMapImplementZ.class);
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapImplementZ.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(NullableConcurrentHashMapImplementZ.class.getGenericInterfaces()));
+ System.out.println();
+ }
+
+ private static void collectionTest() {
+ System.out.println(NullableArrayListExtend.class);
+ System.out.println(Arrays.toString(NullableArrayListExtend.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListExtend.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableArrayListExtendZ.class);
+ System.out.println(Arrays.toString(NullableArrayListExtendZ.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListExtendZ.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableArrayListImplement.class);
+ System.out.println(Arrays.toString(NullableArrayListImplement.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListImplement.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(NullableArrayListImplementZ.class);
+ System.out.println(Arrays.toString(NullableArrayListImplementZ.class.getInterfaces()));
+ System.out.println(Arrays.toString(NullableArrayListImplementZ.class.getGenericInterfaces()));
+ System.out.println();
+ }
+
+ private static void collectionMapTest() {
+ System.out.println(CollectionMapImplements2.class);
+ System.out.println(Arrays.toString(CollectionMapImplements2.class.getInterfaces()));
+ System.out.println(Arrays.toString(CollectionMapImplements2.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapExtendImplement.class);
+ System.out.println(Arrays.toString(CollectionMapExtendImplement.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplement.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapImplements2Integer1.class);
+ System.out.println(Arrays.toString(CollectionMapImplements2Integer1.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapImplements2Integer1.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapExtendImplementInteger1.class);
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger1.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger1.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapImplements2Integer2.class);
+ System.out.println(Arrays.toString(CollectionMapImplements2Integer2.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapImplements2Integer2.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(CollectionMapExtendImplementInteger2.class);
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger2.class.getInterfaces()));
+ System.out.println(
+ Arrays.toString(CollectionMapExtendImplementInteger2.class.getGenericInterfaces()));
+ System.out.println();
+ }
+
+ private static void sqlDateTest() {
+ System.out.println(MySQLDataException.class);
+ System.out.println(Arrays.toString(MySQLDataException.class.getInterfaces()));
+ System.out.println(Arrays.toString(MySQLDataException.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(MyDate.class);
+ System.out.println(Arrays.toString(MyDate.class.getInterfaces()));
+ System.out.println(Arrays.toString(MyDate.class.getGenericInterfaces()));
+ System.out.println();
+
+ System.out.println(MyDateZ.class);
+ System.out.println(Arrays.toString(MyDateZ.class.getInterfaces()));
+ System.out.println(Arrays.toString(MyDateZ.class.getGenericInterfaces()));
+ System.out.println();
+ }
+ }
+
+ interface MyInterface<Z> {
+ void print(Z z);
+ }
+
+ static class NullableConcurrentHashMapExtendDifferentLetters<R, T>
+ extends ConcurrentHashMap<R, T> {
+ NullableConcurrentHashMapExtendDifferentLetters() {
+ super();
+ }
+ }
+
+ static class NullableConcurrentHashMapExtend<K, V> extends ConcurrentHashMap<K, V> {
+ NullableConcurrentHashMapExtend() {
+ super();
+ }
+ }
+
+ static class NullableConcurrentHashMapImplement<K, V> implements ConcurrentMap<K, V> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public V get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public V put(K k, V v) {
+ return null;
+ }
+
+ @Override
+ public V remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<K> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<V> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return null;
+ }
+
+ @Override
+ public V putIfAbsent(@NotNull K k, V v) {
+ return null;
+ }
+
+ @Override
+ public boolean remove(@NotNull Object o, Object o1) {
+ return false;
+ }
+
+ @Override
+ public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+ return false;
+ }
+
+ @Override
+ public V replace(@NotNull K k, @NotNull V v) {
+ return null;
+ }
+ }
+
+ static class NullableConcurrentHashMapExtendZ<K, V> extends ConcurrentHashMap<K, V>
+ implements MyInterface<K> {
+ NullableConcurrentHashMapExtendZ() {
+ super();
+ }
+
+ @Override
+ public void print(K k) {}
+ }
+
+ static class NullableConcurrentHashMapImplementZ<K, V>
+ implements ConcurrentMap<K, V>, MyInterface<K> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public V get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public V put(K k, V v) {
+ return null;
+ }
+
+ @Override
+ public V remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<K> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<V> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return null;
+ }
+
+ @Override
+ public V putIfAbsent(@NotNull K k, V v) {
+ return null;
+ }
+
+ @Override
+ public boolean remove(@NotNull Object o, Object o1) {
+ return false;
+ }
+
+ @Override
+ public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+ return false;
+ }
+
+ @Override
+ public V replace(@NotNull K k, @NotNull V v) {
+ return null;
+ }
+
+ @Override
+ public void print(K k) {}
+ }
+
+ static class NullableArrayListExtend<E> extends ArrayList<E> {
+ NullableArrayListExtend() {
+ super();
+ }
+ }
+
+ static class NullableArrayListImplement<E> implements List<E> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] ts) {
+ return null;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public E get(int i) {
+ return null;
+ }
+
+ @Override
+ public E set(int i, E e) {
+ return null;
+ }
+
+ @Override
+ public void add(int i, E e) {}
+
+ @Override
+ public E remove(int i) {
+ return null;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return 0;
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return 0;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator(int i) {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public List<E> subList(int i, int i1) {
+ return null;
+ }
+ }
+
+ static class NullableArrayListExtendZ<E> extends ArrayList<E> implements MyInterface<E> {
+ NullableArrayListExtendZ() {
+ super();
+ }
+
+ @Override
+ public void print(E e) {}
+ }
+
+ static class NullableArrayListImplementZ<E> implements List<E>, MyInterface<E> {
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] ts) {
+ return null;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection<?> collection) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public E get(int i) {
+ return null;
+ }
+
+ @Override
+ public E set(int i, E e) {
+ return null;
+ }
+
+ @Override
+ public void add(int i, E e) {}
+
+ @Override
+ public E remove(int i) {
+ return null;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return 0;
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return 0;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public ListIterator<E> listIterator(int i) {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public List<E> subList(int i, int i1) {
+ return null;
+ }
+
+ @Override
+ public void print(E e) {}
+ }
+
+ static class CollectionMapImplements2<R, C> implements Iterable<R>, Map<R, C> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public C get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public C put(R r, C c) {
+ return null;
+ }
+
+ @Override
+ public C remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<R> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<C> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<R, C>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapExtendImplement<R, C> extends HashMap<R, C> implements Iterable<R> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public C get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public C put(R r, C c) {
+ return null;
+ }
+
+ @Override
+ public C remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<R> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<C> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<R, C>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapImplements2Integer1<C>
+ implements Iterable<PathClassLoader>, Map<PathClassLoader, C> {
+ @NotNull
+ @Override
+ public Iterator<PathClassLoader> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public C get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public C put(PathClassLoader unsafe, C c) {
+ return null;
+ }
+
+ @Override
+ public C remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends PathClassLoader, ? extends C> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<PathClassLoader> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<C> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<PathClassLoader, C>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapExtendImplementInteger1<C> extends HashMap<PathClassLoader, C>
+ implements Iterable<PathClassLoader> {
+ @NotNull
+ @Override
+ public Iterator<PathClassLoader> iterator() {
+ return null;
+ }
+ }
+
+ static class CollectionMapImplements2Integer2<R> implements Iterable<R>, Map<R, PathClassLoader> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean containsKey(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean containsValue(Object o) {
+ return false;
+ }
+
+ @Override
+ public PathClassLoader get(Object o) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public PathClassLoader put(R r, PathClassLoader unsafe) {
+ return null;
+ }
+
+ @Override
+ public PathClassLoader remove(Object o) {
+ return null;
+ }
+
+ @Override
+ public void putAll(@NotNull Map<? extends R, ? extends PathClassLoader> map) {}
+
+ @Override
+ public void clear() {}
+
+ @NotNull
+ @Override
+ public Set<R> keySet() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Collection<PathClassLoader> values() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Set<Entry<R, PathClassLoader>> entrySet() {
+ return null;
+ }
+ }
+
+ static class CollectionMapExtendImplementInteger2<R> extends HashMap<R, PathClassLoader>
+ implements Iterable<R> {
+ @NotNull
+ @Override
+ public Iterator<R> iterator() {
+ return null;
+ }
+ }
+
+ // SQLDataException implements Iterable<Throwable>.
+ static class MySQLDataException extends SQLDataException {}
+
+ // java.util.Date for the extra dispatch case.
+ static class MyDate extends Date {}
+
+ static class MyDateZ<Z> extends Date implements MyInterface<Z> {
+ @Override
+ public void print(Z z) {}
+ }
+}