Desugared library: duplicate APIs
- Duplicate APIs of implementors of emulated interfaces
on override to avoid the behavior being different when
a method is called from the library.
Change-Id: I73f7550cf3cbfb7b9c13476e30a85668b28fbcac
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 4076ce3..7a64973 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -209,6 +209,7 @@
// InterfaceMethodRewriter is needed for emulated interfaces.
// LambdaRewriter is needed because if it is missing there are invoke custom on
// default/static interface methods, and this is not supported by the compiler.
+ // DesugaredLibraryAPIConverter is here to duplicate APIs.
// The rest is nulled out. In addition the rewriting logic fails without lambda rewriting.
this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
this.interfaceMethodRewriter =
@@ -216,6 +217,7 @@
? null
: new InterfaceMethodRewriter(appView, this);
this.lambdaRewriter = new LambdaRewriter(appView, this);
+ this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView);
this.twrCloseResourceRewriter = null;
this.lambdaMerger = null;
this.covariantReturnTypeAnnotationTransformer = null;
@@ -234,7 +236,6 @@
this.typeChecker = null;
this.d8NestBasedAccessDesugaring = null;
this.stringSwitchRemover = null;
- this.desugaredLibraryAPIConverter = null;
this.serviceLoaderRewriter = null;
this.methodOptimizationInfoCollector = null;
return;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index aebe043..4483eca 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -130,16 +130,19 @@
return;
}
DexMethod method = code.method.method;
- if (appView.rewritePrefix.hasRewrittenType(method.holder) || method.holder.isArrayType()) {
+ if (method.holder.isArrayType()
+ || !appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto)
+ || appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getEmulateLibraryInterface()
+ .containsKey(method.holder)) {
return;
}
DexClass dexClass = appView.definitionFor(method.holder);
if (dexClass == null) {
return;
}
- if (!appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto)) {
- return;
- }
if (overridesLibraryMethod(dexClass, method)) {
generateCallBack(dexClass, code.method);
}
@@ -164,17 +167,13 @@
if (dexClass.superType != factory.objectType) {
workList.add(dexClass.superType);
}
- if (!dexClass.isLibraryClass()) {
+ if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
continue;
}
DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method);
if (dexEncodedMethod != null) {
- if (appView
- .options()
- .desugaredLibraryConfiguration
- .getEmulateLibraryInterface()
- .containsKey(dexClass.type)
- || appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
+ // In this case, the object will be wrapped.
+ if (appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
return false;
}
foundOverrideToRewrite = true;
@@ -184,14 +183,10 @@
}
private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
- if (trackedCallBackAPIs != null) {
- trackedCallBackAPIs.add(originalMethod.method);
- }
- DexMethod methodToInstall =
- methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
if (dexClass.isInterface()
&& originalMethod.isDefaultMethod()
- && !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
+ && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
+ || appView.options().isDesugaredLibraryCompilation())) {
// Interface method desugaring has been performed before and all the call-backs will be
// generated in all implementors of the interface. R8 cannot introduce new
// default methods at this point, but R8 does not need to do anything (the interface
@@ -199,17 +194,14 @@
// support the call-back correctly).
return;
}
- CfCode cfCode =
- new APIConverterWrapperCfCodeProvider(
- appView, originalMethod.method, null, this, dexClass.isInterface())
- .generateCfCode();
- DexEncodedMethod newDexEncodedMethod =
- wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
- newDexEncodedMethod.setCode(cfCode, appView);
- addCallBackSignature(dexClass, newDexEncodedMethod);
+ if (trackedCallBackAPIs != null) {
+ trackedCallBackAPIs.add(originalMethod.method);
+ }
+ addCallBackSignature(dexClass, originalMethod);
}
private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
+ assert dexClass.type == method.method.holder;
callBackMethods.putIfAbsent(dexClass, new HashSet<>());
callBackMethods.get(dexClass).add(method);
}
@@ -240,12 +232,31 @@
generateTrackDesugaredAPIWarnings(trackedAPIs, "");
generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
}
- wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
for (DexClass dexClass : callBackMethods.keySet()) {
- Set<DexEncodedMethod> dexEncodedMethods = callBackMethods.get(dexClass);
+ Set<DexEncodedMethod> dexEncodedMethods =
+ generateCallbackMethods(callBackMethods.get(dexClass), dexClass);
dexClass.appendVirtualMethods(dexEncodedMethods);
irConverter.processMethodsConcurrently(dexEncodedMethods, executorService);
}
+ wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
+ }
+
+ private Set<DexEncodedMethod> generateCallbackMethods(
+ Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
+ Set<DexEncodedMethod> newDexEncodedMethods = new HashSet<>();
+ for (DexEncodedMethod originalMethod : originalMethods) {
+ DexMethod methodToInstall =
+ methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+ CfCode cfCode =
+ new APIConverterWrapperCfCodeProvider(
+ appView, originalMethod.method, null, this, dexClass.isInterface())
+ .generateCfCode();
+ DexEncodedMethod newDexEncodedMethod =
+ wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+ newDexEncodedMethod.setCode(cfCode, appView);
+ newDexEncodedMethods.add(newDexEncodedMethod);
+ }
+ return newDexEncodedMethods;
}
private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index e40252f..5d7c31f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -137,7 +136,7 @@
if (dexClass == null) {
return false;
}
- return dexClass.isLibraryClass();
+ return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
}
DexType getTypeWrapper(DexType type) {
@@ -153,9 +152,10 @@
}
private DexType createWrapperType(DexType type, String suffix) {
+ String prefix = appView.options().isDesugaredLibraryCompilation() ? "lib$" : "";
return factory.createType(
DescriptorUtils.javaTypeToDescriptor(
- WRAPPER_PREFIX + type.toString().replace('.', '$') + suffix));
+ WRAPPER_PREFIX + prefix + type.toString().replace('.', '$') + suffix));
}
private DexType getWrapper(
@@ -183,7 +183,8 @@
assert pair.getSecond() == null;
DexClass dexClass = appView.definitionFor(type);
// The dexClass should be a library class, so it cannot be null.
- assert dexClass != null && dexClass.isLibraryClass();
+ assert dexClass != null
+ && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
if (dexClass.accessFlags.isFinal()) {
throw appView
.options()
@@ -209,7 +210,7 @@
return synthesizeWrapper(
vivifiedTypeFor(type),
dexClass,
- synthesizeVirtualMethodsForTypeWrapper(dexClass.asLibraryClass(), wrapperField),
+ synthesizeVirtualMethodsForTypeWrapper(dexClass, wrapperField),
wrapperField);
}
@@ -221,7 +222,7 @@
return synthesizeWrapper(
type,
dexClass,
- synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass.asLibraryClass(), wrapperField),
+ synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass, wrapperField),
wrapperField);
}
@@ -274,7 +275,7 @@
}
private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
- DexLibraryClass dexClass, DexEncodedField wrapperField) {
+ DexClass dexClass, DexEncodedField wrapperField) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only types in their signature, but each method the wrapper forwards
@@ -318,7 +319,7 @@
}
private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
- DexLibraryClass dexClass, DexEncodedField wrapperField) {
+ DexClass dexClass, DexEncodedField wrapperField) {
List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only vivified types in their signature, but each method the wrapper
@@ -334,7 +335,8 @@
Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
for (DexEncodedMethod dexEncodedMethod : dexMethods) {
DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
- assert holderClass != null;
+ assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+ boolean isInterface = holderClass == null || holderClass.isInterface();
DexMethod methodToInstall =
DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
dexEncodedMethod.method, wrapperField.field.holder, appView);
@@ -346,11 +348,7 @@
} else {
cfCode =
new APIConverterWrapperCfCodeProvider(
- appView,
- dexEncodedMethod.method,
- wrapperField.field,
- converter,
- holderClass.isInterface())
+ appView, dexEncodedMethod.method, wrapperField.field, converter, isInterface)
.generateCfCode();
}
DexEncodedMethod newDexEncodedMethod =
@@ -399,7 +397,7 @@
code);
}
- private List<DexEncodedMethod> allImplementedMethods(DexLibraryClass libraryClass) {
+ private List<DexEncodedMethod> allImplementedMethods(DexClass libraryClass) {
LinkedList<DexClass> workList = new LinkedList<>();
List<DexEncodedMethod> implementedMethods = new ArrayList<>();
workList.add(libraryClass);
@@ -423,8 +421,11 @@
}
for (DexType itf : dexClass.interfaces.values) {
DexClass itfClass = appView.definitionFor(itf);
- assert itfClass != null; // Cannot be null since we started from a LibraryClass.
- workList.add(itfClass);
+ // Cannot be null in program since we started from a LibraryClass.
+ assert itfClass != null || appView.options().isDesugaredLibraryCompilation();
+ if (itfClass != null) {
+ workList.add(itfClass);
+ }
}
if (dexClass.superType != factory.objectType) {
DexClass superClass = appView.definitionFor(dexClass.superType);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 1a5c127..ae8012a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -5,12 +5,11 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.APIConversionTestBase;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -33,7 +32,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class CustomCollectionTest extends CoreLibDesugarTestBase {
+public class CustomCollectionTest extends APIConversionTestBase {
private final TestParameters parameters;
private final boolean shrinkDesugaredLibrary;
@@ -63,11 +62,10 @@
.compile()
.inspect(
inspector -> {
- this.assertNoWrappers(inspector);
this.assertCustomCollectionCallsCorrect(inspector, false);
})
.addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibrary,
+ this::buildDesugaredLibraryWithConversionExtension,
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
@@ -82,13 +80,6 @@
// Expected output is emulated interfaces expected output.
assertLines2By2Correct(stdOut);
}
- String[] split = stdErr.split("Could not find method");
- if (split.length > 2) {
- fail("Could not find multiple methods");
- } else if (split.length == 2) {
- // On some VMs the Serialized lambda code is missing.
- assertTrue(stdErr.contains("SerializedLambda"));
- }
}
@Test
@@ -108,7 +99,6 @@
.compile()
.inspect(
inspector -> {
- this.assertNoWrappers(inspector);
this.assertCustomCollectionCallsCorrect(inspector, true);
})
.addDesugaredCoreLibraryRunClassPath(
@@ -121,11 +111,6 @@
assertResultCorrect(r8TestRunResult.getStdOut(), r8TestRunResult.getStdErr());
}
- private void assertNoWrappers(CodeInspector inspector) {
- assertTrue(inspector.allClasses().stream().noneMatch(cl -> cl.getOriginalName().startsWith(
- DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
- }
-
private void assertCustomCollectionCallsCorrect(CodeInspector inspector, boolean r8) {
MethodSubject direct = inspector.clazz(EXECUTOR).uniqueMethodWithName("directTypes");
// TODO(b/134732760): Due to memberRebinding, R8 is not as precise as D8 regarding
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index b60de38..df86791 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
@@ -86,6 +87,9 @@
clazz.getOriginalName().startsWith("j$.")
|| clazz
.getOriginalName()
+ .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)
+ || clazz
+ .getOriginalName()
.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)));
assertThat(inspector.clazz("j$.time.Clock"), isPresent());
// Above N the following classes are removed instead of being desugared.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
index fc2e028..29b316d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
@@ -51,6 +51,10 @@
}
protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel) {
+ return buildDesugaredLibraryWithConversionExtension(apiLevel, "", false);
+ }
+
+ protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel,String keepRules, boolean shrink) {
Path[] timeConversionClasses;
try {
timeConversionClasses = getConversionClasses();
@@ -59,6 +63,6 @@
}
ArrayList<Path> paths = new ArrayList<>();
Collections.addAll(paths, timeConversionClasses);
- return buildDesugaredLibrary(apiLevel, "", false, paths);
+ return buildDesugaredLibrary(apiLevel, keepRules, shrink, paths);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
new file mode 100644
index 0000000..79cad2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class DuplicateAPIDesugaredLibTest extends APIConversionTestBase {
+
+ @Test
+ public void testLib() throws Exception {
+ Box<Path> desugaredLibBox = new Box<>();
+ Path customLib =
+ testForD8()
+ .addProgramClasses(CustomLibClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip();
+ String stdOut =
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ (AndroidApiLevel api) -> {
+ desugaredLibBox.set(this.buildDesugaredLibraryWithConversionExtension(api));
+ return desugaredLibBox.get();
+ },
+ AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertDupMethod(new CodeInspector(desugaredLibBox.get()));
+ assertLines2By2Correct(stdOut);
+ }
+
+ private void assertDupMethod(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz("j$.util.concurrent.ConcurrentHashMap");
+ assertEquals(
+ 2,
+ clazz.virtualMethods().stream().filter(m -> m.getOriginalName().equals("forEach")).count());
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ Map<Integer, Double> map = new ConcurrentHashMap<>();
+ map.put(1, 1.1);
+ map.put(2, 2.2);
+ BiConsumer<Integer, Double> biConsumer = (x, y) -> System.out.print(" " + x + " " + y);
+ map.forEach(biConsumer);
+ System.out.println();
+ CustomLibClass.javaForEach(map, biConsumer);
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+
+ @SuppressWarnings("WeakerAccess")
+ public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
+ map.forEach(consumer);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
new file mode 100644
index 0000000..31ca3b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class DuplicateAPIProgramTest extends APIConversionTestBase {
+
+ @Test
+ public void testMap() throws Exception {
+ Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ String stdOut =
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class, MyMap.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .inspect(this::assertDupMethod)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertLines2By2Correct(stdOut);
+ }
+
+ private void assertDupMethod(CodeInspector i) {
+ assertEquals(
+ 2,
+ i.clazz(MyMap.class).virtualMethods().stream()
+ .filter(m -> m.getOriginalName().equals("forEach"))
+ .count());
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ IdentityHashMap<Integer, Double> map = new MyMap<>();
+ map.put(1, 1.1);
+ map.put(2, 2.2);
+ BiConsumer<Integer, Double> biConsumer = (x, y) -> System.out.print(" " + x + " " + y);
+ map.forEach(biConsumer);
+ System.out.println();
+ CustomLibClass.javaForEach(map, biConsumer);
+ }
+ }
+
+ public static class MyMap<K, V> extends IdentityHashMap<K, V> {
+
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ System.out.print("ForEach");
+ super.forEach(action);
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+ @SuppressWarnings("WeakerAccess")
+ public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
+ map.forEach(consumer);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
index 6e791b6..a281c3d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
@@ -60,6 +60,12 @@
"java.util.SortedSet": "j$.util.SortedSet",
"java.util.Set": "j$.util.Set",
"java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+ },
+ "custom_conversion": {
+ "java.util.Optional": "j$.util.OptionalConversions",
+ "java.util.OptionalDouble": "j$.util.OptionalConversions",
+ "java.util.OptionalInt": "j$.util.OptionalConversions",
+ "java.util.OptionalLong": "j$.util.OptionalConversions"
}
}
],
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
index 2d2f865..5a1fd0f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
@@ -7,21 +7,16 @@
import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
-import static junit.framework.TestCase.assertTrue;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.endsWith;
-import static org.hamcrest.CoreMatchers.not;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -120,7 +115,6 @@
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.compile()
- .inspect(this::assertNoConversions)
.withArt6Plus64BitsLib()
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
@@ -132,15 +126,6 @@
endsWith(StringUtils.lines("ConcurrentModification: SUCCESS")));
}
- private void assertNoConversions(CodeInspector inspector) {
- assertTrue(
- inspector.allClasses().stream()
- .noneMatch(
- cl ->
- cl.getOriginalName()
- .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
- }
-
private Path[] concurrentHashTestToCompile() {
// We exclude WhiteBox.class because of Method handles, they are not supported on old devices
// and the test uses methods not present even on 28.
@@ -191,7 +176,6 @@
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary);
- System.out.println(keepRuleConsumer.get());
for (String className : concurrentHashTestNGTestsToRun()) {
d8TestCompileResult
.run(parameters.getRuntime(), "TestNGMainRunner", verbosity, className)
@@ -202,9 +186,7 @@
// Failure implies a runtime exception.
// We ensure that everything could be resolved (no missing method or class)
// with the assertion on stderr.
- d8TestCompileResult
- .run(parameters.getRuntime(), className).assertSuccess()
- .assertStderrMatches(not(containsString("Could not")));
+ d8TestCompileResult.run(parameters.getRuntime(), className).assertSuccess();
}
}
}