Desugared library: Collection conversion
b:222647019
Change-Id: Id040529cc8d0975abf86ae8d318aca9fa8b635c3
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_path.json b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
index 092b976..a61df55 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_path.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
@@ -63,6 +63,12 @@
"retarget_method_with_emulated_dispatch": {
"java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
},
+ "api_conversion_collection": {
+ "java.nio.channels.AsynchronousFileChannel java.nio.file.spi.FileSystemProvider#newAsynchronousFileChannel(java.nio.file.Path, java.util.Set, java.util.concurrent.ExecutorService, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
+ "java.nio.channels.SeekableByteChannel java.nio.file.spi.FileSystemProvider#newByteChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
+ "java.nio.channels.FileChannel java.nio.file.spi.FileSystemProvider#newFileChannel(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "OpenOption"],
+ "java.util.List java.nio.file.WatchKey#pollEvents()": [-1, "java.nio.file.WatchEvent"]
+ },
"wrapper_conversion": [
"java.nio.channels.AsynchronousChannel",
"java.nio.channels.CompletionHandler",
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
index c458830..ea0f8c0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -44,7 +44,7 @@
import com.android.tools.r8.utils.OptionalBool;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.function.Function;
+import java.util.function.BiFunction;
import java.util.function.Supplier;
import org.objectweb.asm.Opcodes;
@@ -408,9 +408,13 @@
Supplier<UniqueContext> contextSupplier) {
return internalComputeReturnConversion(
invokedMethod,
- returnType ->
+ (returnType, apiConversionCollection) ->
wrapperSynthesizer.ensureConversionMethod(
- returnType, destIsVivified, eventConsumer, contextSupplier),
+ returnType,
+ destIsVivified,
+ apiConversionCollection,
+ eventConsumer,
+ contextSupplier),
context);
}
@@ -422,17 +426,24 @@
Supplier<UniqueContext> contextSupplier) {
return internalComputeReturnConversion(
invokedMethod,
- returnType ->
+ (returnType, apiConversionCollection) ->
wrapperSynthesizer.getExistingProgramConversionMethod(
- returnType, destIsVivified, eventConsumer, contextSupplier),
+ returnType,
+ destIsVivified,
+ apiConversionCollection,
+ eventConsumer,
+ contextSupplier),
context);
}
private DexMethod internalComputeReturnConversion(
- DexMethod invokedMethod, Function<DexType, DexMethod> methodSupplier, ProgramMethod context) {
+ DexMethod invokedMethod,
+ BiFunction<DexType, DexType, DexMethod> methodSupplier,
+ ProgramMethod context) {
DexType returnType = invokedMethod.proto.returnType;
if (wrapperSynthesizer.shouldConvert(returnType, invokedMethod, context)) {
- return methodSupplier.apply(returnType);
+ DexType apiConversionCollection = getReturnApiConversionCollection(invokedMethod);
+ return methodSupplier.apply(returnType, apiConversionCollection);
}
return null;
}
@@ -446,9 +457,9 @@
return internalComputeParameterConversions(
invokedMethod,
wrapperSynthesizer,
- argType ->
+ (argType, apiConversionCollection) ->
wrapperSynthesizer.ensureConversionMethod(
- argType, destIsVivified, eventConsumer, contextSupplier),
+ argType, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier),
context);
}
@@ -461,28 +472,43 @@
return internalComputeParameterConversions(
invokedMethod,
wrapperSynthesizer,
- argType ->
+ (argType, apiConversionCollection) ->
wrapperSynthesizer.getExistingProgramConversionMethod(
- argType, destIsVivified, eventConsumer, contextSupplier),
+ argType, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier),
context);
}
private DexMethod[] internalComputeParameterConversions(
DexMethod invokedMethod,
DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
- Function<DexType, DexMethod> methodSupplier,
+ BiFunction<DexType, DexType, DexMethod> methodSupplier,
ProgramMethod context) {
DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
DexType[] parameters = invokedMethod.proto.parameters.values;
for (int i = 0; i < parameters.length; i++) {
+ DexType apiConversionCollection = getApiConversionCollection(invokedMethod, i);
DexType argType = parameters[i];
if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) {
- parameterConversions[i] = methodSupplier.apply(argType);
+ parameterConversions[i] = methodSupplier.apply(argType, apiConversionCollection);
}
}
return parameterConversions;
}
+ public DexType getReturnApiConversionCollection(DexMethod method) {
+ return getApiConversionCollection(method, method.getArity());
+ }
+
+ public DexType getApiConversionCollection(DexMethod method, int parameterIndex) {
+ DexType[] dexTypes =
+ appView
+ .options()
+ .machineDesugaredLibrarySpecification
+ .getApiConversionCollection()
+ .get(method);
+ return dexTypes == null ? null : dexTypes[parameterIndex];
+ }
+
private DexMethod convertedMethod(
DexMethod method,
boolean parameterDestIsVivified,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 8507816..f98fa3d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider;
import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.CollectionConversionCfCodeProvider;
import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
@@ -137,8 +138,14 @@
public DexMethod ensureConversionMethod(
DexType type,
boolean destIsVivified,
+ DexType apiConversionCollection,
DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
Supplier<UniqueContext> contextSupplier) {
+ if (apiConversionCollection != null) {
+ assert !type.isArrayType();
+ return ensureCollectionConversionMethod(
+ type, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier);
+ }
DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
if (type.isArrayType()) {
@@ -163,6 +170,68 @@
return conversion;
}
+ private DexMethod ensureCollectionConversionMethod(
+ DexType type,
+ boolean destIsVivified,
+ DexType apiConversionCollection,
+ DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ assert type == factory.setType || type == factory.listType;
+ DexMethod conversion =
+ ensureConversionMethod(
+ apiConversionCollection,
+ destIsVivified,
+ null, // We do not support nested collections.
+ eventConsumer,
+ contextSupplier);
+ return ensureCollectionConversionMethod(type, eventConsumer, contextSupplier, conversion);
+ }
+
+ private DexMethod ensureCollectionConversionMethodFromExistingBaseConversion(
+ DexType type,
+ boolean destIsVivified,
+ DexType apiConversionCollection,
+ DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ assert type == factory.setType || type == factory.listType;
+ DexMethod conversion =
+ getExistingProgramConversionMethod(
+ apiConversionCollection,
+ destIsVivified,
+ null, // We do not support nested collections.
+ eventConsumer,
+ contextSupplier);
+ return ensureCollectionConversionMethod(type, eventConsumer, contextSupplier, conversion);
+ }
+
+ private DexMethod ensureCollectionConversionMethod(
+ DexType collectionType,
+ DesugaredLibraryWrapperSynthesizerEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier,
+ DexMethod conversion) {
+ ProgramMethod arrayConversion =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.ARRAY_CONVERSION,
+ contextSupplier.get(),
+ appView,
+ builder ->
+ builder
+ .setProto(factory.createProto(collectionType, collectionType))
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(
+ codeSynthesizor ->
+ new CollectionConversionCfCodeProvider(
+ appView,
+ codeSynthesizor.getHolderType(),
+ collectionType,
+ conversion)
+ .generateCfCode()));
+ eventConsumer.acceptArrayConversion(arrayConversion);
+ return arrayConversion.getReference();
+ }
+
private DexMethod ensureArrayConversionMethod(
DexType type,
DexType srcType,
@@ -171,7 +240,11 @@
Supplier<UniqueContext> contextSupplier) {
DexMethod conversion =
ensureConversionMethod(
- type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier);
+ type.toDimensionMinusOneType(factory),
+ srcType == type,
+ null,
+ eventConsumer,
+ contextSupplier);
return ensureArrayConversionMethod(
srcType, destType, eventConsumer, contextSupplier, conversion);
}
@@ -184,7 +257,11 @@
Supplier<UniqueContext> contextSupplier) {
DexMethod conversion =
getExistingProgramConversionMethod(
- type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier);
+ type.toDimensionMinusOneType(factory),
+ srcType == type,
+ null,
+ eventConsumer,
+ contextSupplier);
return ensureArrayConversionMethod(
srcType, destType, eventConsumer, contextSupplier, conversion);
}
@@ -222,8 +299,14 @@
public DexMethod getExistingProgramConversionMethod(
DexType type,
boolean destIsVivified,
+ DexType apiConversionCollection,
DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
Supplier<UniqueContext> contextSupplier) {
+ if (apiConversionCollection != null) {
+ assert !type.isArrayType();
+ return ensureCollectionConversionMethodFromExistingBaseConversion(
+ type, destIsVivified, apiConversionCollection, eventConsumer, contextSupplier);
+ }
DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
if (type.isArrayType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 027a2ae..9a0dd0e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -48,6 +48,7 @@
static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
static final String API_LEVEL_GREATER_OR_EQUAL_KEY = "api_level_greater_or_equal";
+ static final String API_CONVERSION_COLLECTION = "api_conversion_collection";
static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
static final String WRAPPER_CONVERSION_EXCLUDING_KEY = "wrapper_conversion_excluding";
static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
@@ -262,6 +263,18 @@
builder.putDontRewritePrefix(dontRewritePrefix.getAsString());
}
}
+ if (jsonFlagSet.has(API_CONVERSION_COLLECTION)) {
+ for (Map.Entry<String, JsonElement> methodAndDescription :
+ jsonFlagSet.get(API_CONVERSION_COLLECTION).getAsJsonObject().entrySet()) {
+ JsonArray array = methodAndDescription.getValue().getAsJsonArray();
+ for (int i = 0; i < array.size(); i += 2) {
+ builder.addApiConversionCollection(
+ parseMethod(methodAndDescription.getKey()),
+ array.get(i).getAsInt(),
+ stringDescriptorToDexType(array.get(i + 1).getAsString()));
+ }
+ }
+ }
if (jsonFlagSet.has(REWRITE_DERIVED_PREFIX_KEY)) {
for (Map.Entry<String, JsonElement> prefixToMatch :
jsonFlagSet.get(REWRITE_DERIVED_PREFIX_KEY).getAsJsonObject().entrySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index fc5bcca..7c17a74 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -33,6 +33,7 @@
private final Map<DexField, DexType> retargetStaticField;
private final Map<DexMethod, DexType> retargetMethod;
private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
+ private final Map<DexMethod, DexType[]> apiConversionCollection;
private final Map<DexType, DexType> legacyBackport;
private final Map<DexType, DexType> customConversions;
private final Set<DexMethod> dontRewriteInvocation;
@@ -50,6 +51,7 @@
Map<DexField, DexType> retargetStaticField,
Map<DexMethod, DexType> retargetMethod,
Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
+ Map<DexMethod, DexType[]> apiConversionCollection,
Map<DexType, DexType> legacyBackport,
Map<DexType, DexType> customConversion,
Set<DexMethod> dontRewriteInvocation,
@@ -65,6 +67,7 @@
this.retargetStaticField = retargetStaticField;
this.retargetMethod = retargetMethod;
this.retargetMethodEmulatedDispatch = retargetMethodEmulatedDispatch;
+ this.apiConversionCollection = apiConversionCollection;
this.legacyBackport = legacyBackport;
this.customConversions = customConversion;
this.dontRewriteInvocation = dontRewriteInvocation;
@@ -86,6 +89,7 @@
ImmutableMap.of(),
ImmutableMap.of(),
ImmutableMap.of(),
+ ImmutableMap.of(),
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(),
@@ -109,6 +113,7 @@
retargetStaticField,
retargetMethod,
retargetMethodEmulatedDispatch,
+ apiConversionCollection,
legacyBackport,
customConversions,
dontRewriteInvocation,
@@ -150,6 +155,10 @@
return retargetMethodEmulatedDispatch;
}
+ public Map<DexMethod, DexType[]> getApiConversionCollection() {
+ return apiConversionCollection;
+ }
+
public Map<DexType, DexType> getLegacyBackport() {
return legacyBackport;
}
@@ -201,6 +210,7 @@
private final Map<DexField, DexType> retargetStaticField;
private final Map<DexMethod, DexType> retargetMethod;
private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
+ private final Map<DexMethod, DexType[]> apiConversionCollection;
private final Map<DexType, DexType> legacyBackport;
private final Map<DexType, DexType> customConversions;
private final Set<DexMethod> dontRewriteInvocation;
@@ -223,6 +233,7 @@
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
+ new IdentityHashMap<>(),
Sets.newIdentityHashSet(),
Sets.newIdentityHashSet(),
new IdentityHashMap<>(),
@@ -241,6 +252,7 @@
Map<DexField, DexType> retargetStaticField,
Map<DexMethod, DexType> retargetMethod,
Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
+ Map<DexMethod, DexType[]> apiConversionCollection,
Map<DexType, DexType> backportCoreLibraryMember,
Map<DexType, DexType> customConversions,
Set<DexMethod> dontRewriteInvocation,
@@ -258,6 +270,7 @@
this.retargetStaticField = new IdentityHashMap<>(retargetStaticField);
this.retargetMethod = new IdentityHashMap<>(retargetMethod);
this.retargetMethodEmulatedDispatch = new IdentityHashMap<>(retargetMethodEmulatedDispatch);
+ this.apiConversionCollection = new IdentityHashMap<>(apiConversionCollection);
this.legacyBackport = new IdentityHashMap<>(backportCoreLibraryMember);
this.customConversions = new IdentityHashMap<>(customConversions);
this.dontRewriteInvocation = Sets.newIdentityHashSet();
@@ -370,6 +383,14 @@
return this;
}
+ public void addApiConversionCollection(DexMethod method, int index, DexType type) {
+ DexType[] types =
+ apiConversionCollection.computeIfAbsent(method, k -> new DexType[method.getArity() + 1]);
+ int actualIndex = index == -1 ? method.getArity() : index;
+ assert types[actualIndex] == null;
+ types[actualIndex] = type;
+ }
+
public Builder putLegacyBackport(DexType backportType, DexType rewrittenBackportType) {
put(
legacyBackport,
@@ -410,6 +431,7 @@
ImmutableMap.copyOf(retargetStaticField),
ImmutableMap.copyOf(retargetMethod),
ImmutableMap.copyOf(retargetMethodEmulatedDispatch),
+ ImmutableMap.copyOf(apiConversionCollection),
ImmutableMap.copyOf(legacyBackport),
ImmutableMap.copyOf(customConversions),
ImmutableSet.copyOf(dontRewriteInvocation),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 7367257..5533d92 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -116,6 +116,10 @@
return rewritingFlags.getEmulatedVirtualRetargetThroughEmulatedInterface();
}
+ public Map<DexMethod, DexType[]> getApiConversionCollection() {
+ return rewritingFlags.getApiConversionCollection();
+ }
+
public void forEachRetargetMethod(Consumer<DexMethod> consumer) {
rewritingFlags.forEachRetargetMethod(consumer);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 1a301da..0b8530b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -34,6 +34,7 @@
Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
Map<DexMethod, DexMethod> emulatedVirtualRetargetThroughEmulatedInterface,
+ Map<DexMethod, DexType[]> apiConversionCollection,
Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces,
Map<DexType, List<DexMethod>> wrappers,
Map<DexType, DexType> legacyBackport,
@@ -50,6 +51,7 @@
this.emulatedVirtualRetarget = emulatedVirtualRetarget;
this.emulatedVirtualRetargetThroughEmulatedInterface =
emulatedVirtualRetargetThroughEmulatedInterface;
+ this.apiConversionCollection = apiConversionCollection;
this.emulatedInterfaces = emulatedInterfaces;
this.wrappers = wrappers;
this.legacyBackport = legacyBackport;
@@ -86,6 +88,9 @@
// dispatch. The method has to override an emulated interface method.
private final Map<DexMethod, DexMethod> emulatedVirtualRetargetThroughEmulatedInterface;
+ // Encodes weither specific parameter collections need to be wrapped differently.
+ private final Map<DexMethod, DexType[]> apiConversionCollection;
+
// Emulated interface descriptors.
private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
@@ -130,6 +135,10 @@
return emulatedVirtualRetargetThroughEmulatedInterface;
}
+ public Map<DexMethod, DexType[]> getApiConversionCollection() {
+ return apiConversionCollection;
+ }
+
public void forEachRetargetMethod(Consumer<DexMethod> consumer) {
staticRetarget.keySet().forEach(consumer);
nonEmulatedVirtualRetarget.keySet().forEach(consumer);
@@ -222,6 +231,8 @@
emulatedVirtualRetarget = ImmutableMap.builder();
private final ImmutableMap.Builder<DexMethod, DexMethod>
emulatedVirtualRetargetThroughEmulatedInterface = ImmutableMap.builder();
+ private final ImmutableMap.Builder<DexMethod, DexType[]> apiConversionCollection =
+ ImmutableMap.builder();
private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
ImmutableMap.builder();
private final ImmutableMap.Builder<DexType, List<DexMethod>> wrappers = ImmutableMap.builder();
@@ -275,6 +286,10 @@
emulatedVirtualRetargetThroughEmulatedInterface.put(src, dest);
}
+ public void addApiConversionCollection(DexMethod method, DexType[] dexTypes) {
+ apiConversionCollection.put(method, dexTypes);
+ }
+
public void addWrapper(DexType wrapperConversion, List<DexMethod> methods) {
wrappers.put(wrapperConversion, ImmutableList.copyOf(methods));
}
@@ -313,6 +328,7 @@
nonEmulatedVirtualRetarget.build(),
emulatedVirtualRetarget.build(),
emulatedVirtualRetargetThroughEmulatedInterface.build(),
+ apiConversionCollection.build(),
emulatedInterfaces.build(),
wrappers.build(),
legacyBackport.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 36ad9cf..99d816e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -96,6 +96,7 @@
ComputedApiLevel.unknown());
rewritingFlags.getAmendLibraryMethod().forEach(builder::amendLibraryMethod);
rewritingFlags.getAmendLibraryField().forEach(builder::amendLibraryField);
+ rewritingFlags.getApiConversionCollection().forEach(builder::addApiConversionCollection);
new HumanToMachineRetargetConverter(appInfo)
.convertRetargetFlags(rewritingFlags, builder, this::warnMissingReferences);
new HumanToMachineEmulatedInterfaceConverter(appInfo)
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
index 98ec596..fc80fe8 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -257,4 +257,121 @@
return standardCfCodeFromInstructions(instructions);
}
}
+
+ public static class CollectionConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+ private final DexType collectionType;
+ private final DexMethod conversion;
+
+ public CollectionConversionCfCodeProvider(
+ AppView<?> appView, DexType holder, DexType collectionType, DexMethod conversion) {
+ super(appView, holder);
+ this.collectionType = collectionType;
+ this.conversion = conversion;
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ DexItemFactory factory = appView.dexItemFactory();
+ List<CfInstruction> instructions = new ArrayList<>();
+
+ // if (arg == null) { return null; }
+ generateNullCheck(instructions);
+ instructions.add(
+ new CfFrame(
+ ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+ .put(0, FrameType.initialized(collectionType))
+ .build(),
+ ImmutableDeque.of()));
+
+ ImmutableInt2ReferenceSortedMap<FrameType> locals =
+ ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+ .put(0, FrameType.initialized(collectionType))
+ .put(1, FrameType.initialized(collectionType))
+ .put(2, FrameType.initialized(factory.iteratorType))
+ .build();
+
+ // Collection<E> t1 = new Collection<E>();
+ if (collectionType == factory.setType) {
+ DexType hashSetType = factory.createType("Ljava/util/HashSet;");
+ instructions.add(new CfNew(hashSetType));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKESPECIAL,
+ factory.createMethod(
+ hashSetType,
+ factory.createProto(factory.voidType),
+ factory.constructorMethodName),
+ false));
+ } else {
+ assert collectionType == factory.listType;
+ DexType arrayListType = factory.createType("Ljava/util/ArrayList;");
+ instructions.add(new CfNew(arrayListType));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKESPECIAL,
+ factory.createMethod(
+ arrayListType,
+ factory.createProto(factory.voidType),
+ factory.constructorMethodName),
+ false));
+ }
+ instructions.add(new CfStore(ValueType.OBJECT, 1));
+
+ // Iterator<E> t2 = receiver.iterator();
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKEINTERFACE,
+ factory.createMethod(
+ factory.collectionType, factory.createProto(factory.iteratorType), "iterator"),
+ true));
+ instructions.add(new CfStore(ValueType.OBJECT, 2));
+
+ // while(t2.hasNext())
+ CfLabel returnLabel = new CfLabel();
+ CfLabel loopLabel = new CfLabel();
+ instructions.add(loopLabel);
+ instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+ instructions.add(new CfLoad(ValueType.fromDexType(factory.iteratorType), 2));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKEINTERFACE,
+ factory.createMethod(
+ factory.iteratorType, factory.createProto(factory.booleanType), "hasNext"),
+ true));
+ instructions.add(new CfConstNumber(0, ValueType.INT));
+ instructions.add(new CfIfCmp(If.Type.EQ, ValueType.INT, returnLabel));
+
+ // {t1.add(convert(t2.next());}
+ instructions.add(new CfLoad(ValueType.fromDexType(collectionType), 1));
+ instructions.add(new CfLoad(ValueType.fromDexType(factory.iteratorType), 2));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKEINTERFACE,
+ factory.createMethod(
+ factory.iteratorType,
+ factory.createProto(conversion.getArgumentType(0, true)),
+ "next"),
+ true));
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKEINTERFACE,
+ factory.createMethod(
+ factory.collectionType,
+ factory.createProto(factory.booleanType, factory.objectType),
+ "add"),
+ true));
+ instructions.add(new CfGoto(loopLabel));
+
+ // return t1;
+ instructions.add(returnLabel);
+ instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+ instructions.add(new CfLoad(ValueType.fromDexType(collectionType), 1));
+ instructions.add(new CfReturn(ValueType.fromDexType(collectionType)));
+
+ return standardCfCodeFromInstructions(instructions);
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index f452df5..d9154e7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -49,7 +49,7 @@
public class DesugaredLibraryTestBase extends TestBase {
- private static final boolean FORCE_JDK11_DESUGARED_LIB = false;
+ private static final boolean FORCE_JDK11_DESUGARED_LIB = true;
@BeforeClass
public static void setUpDesugaredLibrary() {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java
new file mode 100644
index 0000000..0a9377f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/ChannelSetTest.java
@@ -0,0 +1,269 @@
+// Copyright (c) 2022, 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.jdk11;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.StandardOpenOption;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+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 ChannelSetTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "bytes written: 11",
+ "String written: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "unsupported");
+ private static final String EXPECTED_RESULT_26 =
+ StringUtils.lines(
+ "bytes written: 11",
+ "String written: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "wrapper start",
+ "bytes read: 11",
+ "String read: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "bytes read: 11",
+ "String read: Hello World");
+ private static Path CUSTOM_LIB;
+
+ @BeforeClass
+ public static void compileCustomLib() throws Exception {
+ CUSTOM_LIB =
+ testForD8(getStaticTemp())
+ .addProgramClasses(CustomLib.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip();
+ }
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ // Skip Android 4.4.4 due to missing libjavacrypto.
+ return buildParameters(
+ BooleanUtils.values(),
+ getTestParameters()
+ .withDexRuntime(Version.V4_0_4)
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build());
+ }
+
+ public ChannelSetTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ private String getExpectedResult() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
+ ? EXPECTED_RESULT_26
+ : EXPECTED_RESULT;
+ }
+
+ private LibraryDesugaringTestConfiguration pathConfiguration() {
+ return LibraryDesugaringTestConfiguration.builder()
+ .setMinApi(parameters.getApiLevel())
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.getDesugarLibJsonForTestingWithPath()))
+ .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+ .withKeepRuleConsumer()
+ .build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ Assume.assumeTrue(isJDK11DesugaredLibrary());
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(getLibraryFile())
+ .addProgramClasses(TestClass.class)
+ .addLibraryClasses(CustomLib.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(pathConfiguration())
+ .compile()
+ .withArt6Plus64BitsLib()
+ .withArtFrameworks()
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ Integer.toString(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Assume.assumeTrue(isJDK11DesugaredLibrary());
+ testForR8(Backend.DEX)
+ .addLibraryFiles(getLibraryFile())
+ .addProgramClasses(TestClass.class)
+ .addLibraryClasses(CustomLib.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .enableCoreLibraryDesugaring(pathConfiguration())
+ .compile()
+ .withArt6Plus64BitsLib()
+ .withArtFrameworks()
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ Integer.toString(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ public static class CustomLib {
+
+ // Answers effectively a Path wrapper.
+ public static Path get(String path) {
+ return Paths.get(path);
+ }
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) throws Throwable {
+ Path path = Files.createTempFile("example", ".txt");
+ String hello = "Hello World";
+ Set<OpenOption> openOptions = new HashSet<>();
+ openOptions.add(LinkOption.NOFOLLOW_LINKS);
+ writeHelloWorldIntoFile(path, hello);
+ readHelloWithPathApis(path, hello, openOptions);
+ // The rest of tests are testing API conversion, which is possible only on + devices.
+ if (Integer.parseInt(args[0]) < 26) {
+ return;
+ }
+ System.out.println("wrapper start");
+ Path pathWrapper = CustomLib.get(path.toString());
+ readHelloWithPathWrapperApis(pathWrapper, hello, openOptions);
+ try {
+ try (SeekableByteChannel channel =
+ pathWrapper.getFileSystem().provider().newByteChannel(path, openOptions)) {
+ readFromChannel(channel, hello.length());
+ }
+ } catch (ProviderMismatchException e) {
+ System.out.println("provider missmatch");
+ }
+ }
+
+ private static void readHelloWithPathWrapperApis(
+ Path pathWrapper, String hello, Set<OpenOption> openOptions)
+ throws IOException, InterruptedException, ExecutionException {
+ try (SeekableByteChannel channel =
+ pathWrapper.getFileSystem().provider().newByteChannel(pathWrapper, openOptions)) {
+ readFromChannel(channel, hello.length());
+ }
+ try (FileChannel channel =
+ pathWrapper.getFileSystem().provider().newFileChannel(pathWrapper, openOptions)) {
+ readFromChannel(channel, hello.length());
+ }
+ try {
+ try (AsynchronousFileChannel channel =
+ pathWrapper
+ .getFileSystem()
+ .provider()
+ .newAsynchronousFileChannel(pathWrapper, openOptions, ForkJoinPool.commonPool())) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(hello.length());
+ Future<Integer> readFuture = channel.read(byteBuffer, 0);
+ // We force the future to await here with get().
+ int read = readFuture.get();
+ System.out.println("bytes read: " + read);
+ System.out.println("String read: " + new String(byteBuffer.array()));
+ }
+ } catch (UnsupportedOperationException e) {
+ System.out.println("unsupported");
+ }
+ }
+
+ private static void readHelloWithPathApis(Path path, String hello, Set<OpenOption> openOptions)
+ throws IOException, InterruptedException, ExecutionException {
+ try (SeekableByteChannel channel =
+ path.getFileSystem().provider().newByteChannel(path, openOptions)) {
+ readFromChannel(channel, hello.length());
+ }
+ try (FileChannel channel =
+ path.getFileSystem().provider().newFileChannel(path, openOptions)) {
+ readFromChannel(channel, hello.length());
+ }
+ try {
+ try (AsynchronousFileChannel channel =
+ path.getFileSystem()
+ .provider()
+ .newAsynchronousFileChannel(path, openOptions, ForkJoinPool.commonPool())) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(hello.length());
+ Future<Integer> readFuture = channel.read(byteBuffer, 0);
+ int read = readFuture.get();
+ System.out.println("bytes read: " + read);
+ System.out.println("String read: " + new String(byteBuffer.array()));
+ }
+ } catch (NoClassDefFoundError | UnsupportedOperationException e) {
+ // ForkJoinPool is missing (officially below Android 21, in practice somewhere below).
+ // The method newAsynchronousFileChannel is unsupported on DesugarFileSystems.
+ System.out.println("unsupported");
+ }
+ }
+
+ private static void writeHelloWorldIntoFile(Path path, String hello) throws IOException {
+ try (SeekableByteChannel channel =
+ Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(hello.getBytes());
+ int write = channel.write(byteBuffer);
+ System.out.println("bytes written: " + write);
+ System.out.println("String written: " + hello);
+ }
+ }
+
+ private static void readFromChannel(SeekableByteChannel channel, int helloWorldSize)
+ throws IOException {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(helloWorldSize);
+ int read = channel.read(byteBuffer);
+ System.out.println("bytes read: " + read);
+ System.out.println("String read: " + new String(byteBuffer.array()));
+ }
+ }
+}