Desugared library API unwrap tests
- Fixed unwrapping
- tests for unwrapping
- tests for wrappers without reverse
wrappers (was broken due to unwrapping)
- patch comments
Bug: 134732760
Change-Id: I52bcf700bb0d81380bf6a39aad577063c2aa1437
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 c87b921..fe410fa 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
@@ -358,8 +358,9 @@
private DexEncodedField synthetizeWrappedValueField(DexType holder, DexType fieldType) {
DexField field = factory.createField(holder, fieldType, factory.wrapperFieldName);
+ // Field is package private to be accessible from convert methods without a getter.
FieldAccessFlags fieldAccessFlags =
- FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_PRIVATE);
+ FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
return new DexEncodedField(field, fieldAccessFlags, DexAnnotationSet.empty(), null);
}
@@ -412,6 +413,7 @@
Map<DexType, Pair<DexType, DexProgramClass>> wrappers,
BiConsumer<DexType, DexProgramClass> generateConversions)
throws ExecutionException {
+ assert verifyAllClassesGenerated();
for (DexType type : wrappers.keySet()) {
DexProgramClass pgrmClass = wrappers.get(type).getSecond();
assert pgrmClass != null;
@@ -421,6 +423,16 @@
}
}
+ private boolean verifyAllClassesGenerated() {
+ for (Pair<DexType, DexProgramClass> pair : vivifiedTypeWrappers.values()) {
+ assert pair.getSecond() != null;
+ }
+ for (Pair<DexType, DexProgramClass> pair : typeWrappers.values()) {
+ assert pair.getSecond() != null;
+ }
+ return true;
+ }
+
private void registerSynthesizedClass(
DexProgramClass synthesizedClass, DexApplication.Builder<?> builder) {
builder.addSynthesizedClass(synthesizedClass, false);
@@ -428,33 +440,41 @@
}
private void generateTypeConversions(DexType type, DexProgramClass synthesizedClass) {
+ Pair<DexType, DexProgramClass> reverse = vivifiedTypeWrappers.get(type);
+ assert reverse == null || reverse.getSecond() != null;
synthesizedClass.addDirectMethod(
synthetizeConversionMethod(
synthesizedClass.type,
type,
converter.vivifiedTypeFor(type),
- typeWrappers.get(type).getFirst()));
+ reverse == null ? null : reverse.getSecond()));
}
private void generateVivifiedTypeConversions(DexType type, DexProgramClass synthesizedClass) {
+ Pair<DexType, DexProgramClass> reverse = typeWrappers.get(type);
synthesizedClass.addDirectMethod(
synthetizeConversionMethod(
synthesizedClass.type,
converter.vivifiedTypeFor(type),
type,
- vivifiedTypeWrappers.get(type).getFirst()));
+ reverse == null ? null : reverse.getSecond()));
}
private DexEncodedMethod synthetizeConversionMethod(
- DexType holder, DexType argType, DexType returnType, DexType reverseWrapperType) {
+ DexType holder, DexType argType, DexType returnType, DexClass reverseWrapperClassOrNull) {
DexMethod method =
factory.createMethod(
holder, factory.createProto(returnType, argType), factory.convertMethodName);
+
+ DexField uniqueFieldOrNull =
+ reverseWrapperClassOrNull == null
+ ? null
+ : reverseWrapperClassOrNull.instanceFields().get(0).field;
CfCode cfCode =
new APIConverterWrapperConversionCfCodeProvider(
appView,
argType,
- reverseWrapperType,
+ uniqueFieldOrNull,
factory.createField(holder, returnType, factory.wrapperFieldName))
.generateCfCode();
return newSynthesizedMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 00eb005..c98f1fa 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -198,14 +198,14 @@
public static class APIConverterWrapperConversionCfCodeProvider extends SyntheticCfCodeProvider {
DexType argType;
- DexType reverseWrapperType;
+ DexField reverseWrapperField;
DexField wrapperField;
public APIConverterWrapperConversionCfCodeProvider(
- AppView<?> appView, DexType argType, DexType reverseWrapperType, DexField wrapperField) {
+ AppView<?> appView, DexType argType, DexField reverseWrapperField, DexField wrapperField) {
super(appView, wrapperField.holder);
this.argType = argType;
- this.reverseWrapperType = reverseWrapperType;
+ this.reverseWrapperField = reverseWrapperField;
this.wrapperField = wrapperField;
}
@@ -222,16 +222,18 @@
instructions.add(new CfReturn(ValueType.OBJECT));
instructions.add(nullDest);
+ // This part is skipped if there is no reverse wrapper.
// if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
- if (reverseWrapperType != null) {
+ if (reverseWrapperField != null) {
CfLabel unwrapDest = new CfLabel();
instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
- instructions.add(new CfInstanceOf(reverseWrapperType));
+ instructions.add(new CfInstanceOf(reverseWrapperField.holder));
instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
- instructions.add(new CfCheckCast(reverseWrapperType));
- instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
- instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.type)));
+ instructions.add(new CfCheckCast(reverseWrapperField.holder));
+ instructions.add(
+ new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
+ instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
instructions.add(unwrapDest);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java
index 0b34b7f..6c069d9 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java
@@ -75,7 +75,8 @@
}
// This class will be put at compilation time as library and on the runtime class path.
- // This class is convenient for easy testing. None of the methods make sense.
+ // 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("all")
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
index 71e1a3f..ed5ca26 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
@@ -87,7 +87,8 @@
}
// This class will be put at compilation time as library and on the runtime class path.
- // This class is convenient for easy testing. None of the methods make sense.
+ // 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 {
public static ZonedDateTime mix(ZonedDateTime zonedDateTime1, ZonedDateTime zonedDateTime2) {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java
index 18480ee..cd6dd54 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java
@@ -42,7 +42,8 @@
}
// This class will be put at compilation time as library and on the runtime class path.
- // This class is convenient for easy testing. None of the methods make sense.
+ // 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 {
public static MonthDay mix(
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionsTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
similarity index 69%
rename from src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionsTest.java
rename to src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
index 1743557..702eb05 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
@@ -4,21 +4,29 @@
package com.android.tools.r8.desugar.corelib.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.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.BooleanSupplier;
import java.util.function.DoublePredicate;
+import java.util.function.DoubleSupplier;
import java.util.function.Function;
+import java.util.function.IntSupplier;
import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
+import java.util.stream.Collectors;
import org.junit.Test;
-public class FunctionConversionsTest extends APIConversionTestBase {
+public class FunctionConversionTest extends APIConversionTestBase {
@Test
public void testFunctionComposition() throws Exception {
@@ -30,12 +38,33 @@
.addLibraryClasses(CustomLibClass.class)
.enableCoreLibraryDesugaring(AndroidApiLevel.B)
.compile()
+ .inspect(this::assertSingleWrappers)
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
.addRunClasspathFiles(customLib)
.run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
.assertSuccessWithOutput(
- StringUtils.lines("Object1 Object2 Object3", "2", "false", "3", "true"));
+ StringUtils.lines("Object1 Object2 Object3", "2", "false", "3", "true", "5", "42.0"));
+ }
+
+ private void assertSingleWrappers(CodeInspector i) {
+ List<FoundClassSubject> intSupplierWrapperClasses =
+ i.allClasses().stream()
+ .filter(c -> c.getOriginalName().contains("IntSupplier"))
+ .collect(Collectors.toList());
+ assertEquals(
+ "Expected 1 IntSupplier wrapper but got " + intSupplierWrapperClasses,
+ 1,
+ intSupplierWrapperClasses.size());
+
+ List<FoundClassSubject> doubleSupplierWrapperClasses =
+ i.allClasses().stream()
+ .filter(c -> c.getOriginalName().contains("DoubleSupplier"))
+ .collect(Collectors.toList());
+ assertEquals(
+ "Expected 1 DoubleSupplier wrapper but got " + doubleSupplierWrapperClasses,
+ 1,
+ doubleSupplierWrapperClasses.size());
}
static class Executor {
@@ -53,6 +82,9 @@
DoublePredicate doublePredicate =
CustomLibClass.mixPredicate(d -> d > 1.0, d -> d == 2.0, d -> d < 3.0);
System.out.println(doublePredicate.test(2.0));
+ // Reverse wrapper should not exist.
+ System.out.println(CustomLibClass.extractInt(() -> 5));
+ System.out.println(CustomLibClass.getDoubleSupplier().getAsDouble());
}
static class Object1 {}
@@ -85,7 +117,8 @@
}
// This class will be put at compilation time as library and on the runtime class path.
- // This class is convenient for easy testing. None of the methods make sense.
+ // 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 {
public static <T, Q, R> Function<T, R> mixFunction(Function<T, Q> f1, Function<Q, R> f2) {
@@ -110,5 +143,13 @@
DoublePredicate predicate1, DoublePredicate predicate2, DoublePredicate predicate3) {
return predicate1.and(predicate2).and(predicate3);
}
+
+ public static int extractInt(IntSupplier supplier) {
+ return supplier.getAsInt();
+ }
+
+ public static DoubleSupplier getDoubleSupplier() {
+ return () -> 42.0;
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/UnwrapConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/UnwrapConversionTest.java
new file mode 100644
index 0000000..8fe7504
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/UnwrapConversionTest.java
@@ -0,0 +1,70 @@
+// 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.corelib.conversionTests;
+
+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.StringUtils;
+import java.nio.file.Path;
+import java.util.function.DoubleConsumer;
+import java.util.function.IntConsumer;
+import org.junit.Test;
+
+public class UnwrapConversionTest extends APIConversionTestBase {
+
+ @Test
+ public void testUnwrap() throws Exception {
+ Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccessWithOutput(StringUtils.lines("true", "true"));
+ }
+
+ static class Executor {
+
+ @SuppressWarnings("all")
+ public static void main(String[] args) {
+ // Type wrapper.
+ IntConsumer intConsumer = i -> {};
+ IntConsumer unwrappedIntConsumer = CustomLibClass.identity(intConsumer);
+ System.out.println(intConsumer == unwrappedIntConsumer);
+
+ // Vivified wrapper.
+ DoubleConsumer consumer = CustomLibClass.getConsumer();
+ System.out.println(CustomLibClass.testConsumer(consumer));
+ }
+ }
+
+ // 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 {
+
+ private static DoubleConsumer consumer = d -> {};
+
+ @SuppressWarnings("WeakerAccess")
+ public static IntConsumer identity(IntConsumer intConsumer) {
+ return intConsumer;
+ }
+
+ public static DoubleConsumer getConsumer() {
+ return consumer;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public static boolean testConsumer(DoubleConsumer doubleConsumer) {
+ return doubleConsumer == consumer;
+ }
+ }
+}