Desugared library: Support all callbacks from library
- add minimalCallbacks option in DesugaredLibraryConfiguration
Bug: 163880202
Change-Id: I0cd64cf8c2b966d109cf4d8e14b8e364b118092c
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 945ad60..98ceb80 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,9 +2,10 @@
"configuration_format_version": 3,
"group_id" : "com.tools.android",
"artifact_id" : "desugar_jdk_libs",
- "version": "1.0.12",
+ "version": "1.1.0",
"required_compilation_api_level": 26,
"synthesized_library_classes_package_prefix": "j$.",
+ "support_all_callbacks_from_library": true,
"common_flags": [
{
"api_level_below_or_equal": 25,
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 c337afe..9356caa 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
@@ -186,6 +186,10 @@
.containsKey(method.getHolderType())) {
return false;
}
+ if (!appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary
+ && appView.options().isDesugaredLibraryCompilation()) {
+ return false;
+ }
return overridesLibraryMethod(method);
}
@@ -212,6 +216,9 @@
if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
continue;
}
+ if (!shouldGenerateCallbacksForEmulateInterfaceAPIs(dexClass)) {
+ continue;
+ }
DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method.getReference());
if (dexEncodedMethod != null) {
// In this case, the object will be wrapped.
@@ -224,6 +231,16 @@
return foundOverrideToRewrite;
}
+ private boolean shouldGenerateCallbacksForEmulateInterfaceAPIs(DexClass dexClass) {
+ if (appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary) {
+ return true;
+ }
+ Map<DexType, DexType> emulateLibraryInterfaces =
+ appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+ return !(emulateLibraryInterfaces.containsKey(dexClass.type)
+ || emulateLibraryInterfaces.containsValue(dexClass.type));
+ }
+
private synchronized void registerCallback(ProgramMethod method) {
// In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
// methods will be desugared.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 542c5d7..9896987 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -36,12 +36,15 @@
public class DesugaredLibraryConfiguration {
public static final String FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX = "j$/";
+ public static final boolean FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY = true;
+
public static final DesugaredLibraryConfiguration EMPTY_DESUGARED_LIBRARY_CONFIGURATION =
new DesugaredLibraryConfiguration(
AndroidApiLevel.B,
false,
FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
null,
+ FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
ImmutableMap.of(),
ImmutableMap.of(),
ImmutableMap.of(),
@@ -51,11 +54,18 @@
ImmutableList.of(),
ImmutableList.of());
- // TODO(b/158632510): should use DexString, DexType, DexMethod or so on when possible.
private final AndroidApiLevel requiredCompilationAPILevel;
private final boolean libraryCompilation;
private final String synthesizedLibraryClassesPackagePrefix;
private final String identifier;
+ // Setting supportAllCallbacksFromLibrary reduces the number of generated call-backs,
+ // more specifically:
+ // - no call-back is generated for emulated interface method overrides (forEach, etc.)
+ // - no call-back is generated inside the desugared library itself.
+ // Such setting decreases significantly the desugared library dex file, but virtual calls from
+ // within the library to desugared library classes instances as receiver may be incorrect, for
+ // example the method forEach in Iterable may be executed over a concrete implementation.
+ public final boolean supportAllCallbacksFromLibrary;
private final Map<String, String> rewritePrefix;
private final Map<DexType, DexType> emulateLibraryInterface;
private final Map<DexString, Map<DexType, DexType>> retargetCoreLibMember;
@@ -76,6 +86,7 @@
true,
FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
"testingOnlyVersion",
+ FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
prefix,
ImmutableMap.of(),
ImmutableMap.of(),
@@ -95,6 +106,7 @@
boolean libraryCompilation,
String packagePrefix,
String identifier,
+ boolean supportAllCallbacksFromLibrary,
Map<String, String> rewritePrefix,
Map<DexType, DexType> emulateLibraryInterface,
Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
@@ -107,6 +119,7 @@
this.libraryCompilation = libraryCompilation;
this.synthesizedLibraryClassesPackagePrefix = packagePrefix;
this.identifier = identifier;
+ this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
this.rewritePrefix = rewritePrefix;
this.emulateLibraryInterface = emulateLibraryInterface;
this.retargetCoreLibMember = retargetCoreLibMember;
@@ -204,6 +217,7 @@
private Set<DexType> wrapperConversions = Sets.newIdentityHashSet();
private List<Pair<DexType, DexString>> dontRewriteInvocation = new ArrayList<>();
private List<String> extraKeepRules = Collections.emptyList();
+ private boolean supportAllCallbacksFromLibrary = FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY;
private Builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
this.factory = dexItemFactory;
@@ -344,6 +358,10 @@
return factory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass));
}
+ public void setSupportAllCallbacksFromLibrary(boolean supportAllCallbacksFromLibrary) {
+ this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
+ }
+
public DesugaredLibraryConfiguration build() {
validate();
return new DesugaredLibraryConfiguration(
@@ -351,6 +369,7 @@
libraryCompilation,
synthesizedLibraryClassesPackagePrefix,
identifier,
+ supportAllCallbacksFromLibrary,
ImmutableMap.copyOf(rewritePrefix),
ImmutableMap.copyOf(emulateLibraryInterface),
ImmutableMap.copyOf(retargetCoreLibMember),
@@ -373,5 +392,6 @@
origin));
}
}
+
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index 7586210..8b81bf4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -47,6 +47,7 @@
static final String DONT_REWRITE_KEY = "dont_rewrite";
static final String BACKPORT_KEY = "backport";
static final String SHRINKER_CONFIG_KEY = "shrinker_config";
+ static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library";
private final DexItemFactory dexItemFactory;
private final Reporter reporter;
@@ -107,6 +108,9 @@
throw reporter.fatalError(
new StringDiagnostic(
"Unsupported desugared library configuration version, please upgrade the D8/R8"
+ + formatVersion
+ + " - "
+ + MAX_SUPPORTED_VERSION
+ " compiler.",
origin));
}
@@ -149,6 +153,12 @@
}
configurationBuilder.setExtraKeepRules(extraKeepRules);
}
+
+ if (jsonConfig.has(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY)) {
+ boolean supportAllCallbacksFromLibrary =
+ jsonConfig.get(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY).getAsBoolean();
+ configurationBuilder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary);
+ }
configurationAmender.accept(configurationBuilder);
DesugaredLibraryConfiguration config = configurationBuilder.build();
configurationBuilder = null;
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 b02b2e9..c7465ca 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
@@ -21,6 +21,8 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
@@ -67,6 +69,11 @@
return buildDesugaredLibrary(apiLevel, "", false);
}
+ protected Path buildDesugaredLibrary(
+ AndroidApiLevel apiLevel, Consumer<InternalOptions> optionsModifier) {
+ return buildDesugaredLibrary(apiLevel, "", false, ImmutableList.of(), optionsModifier);
+ }
+
protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel, String keepRules) {
return buildDesugaredLibrary(apiLevel, keepRules, true);
}
@@ -186,6 +193,21 @@
return desugaredLib;
}
+ protected DesugaredLibraryConfiguration configurationWithSupportAllCallbacksFromLibrary(
+ InternalOptions options,
+ boolean libraryCompilation,
+ TestParameters parameters,
+ boolean supportAllCallbacksFromLibrary) {
+ return new DesugaredLibraryConfigurationParser(
+ options.dexItemFactory(),
+ options.reporter,
+ libraryCompilation,
+ parameters.getApiLevel().getLevel())
+ .parse(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING),
+ builder -> builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary));
+ }
+
public interface KeepRuleConsumer extends StringConsumer {
String get();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index 14f3ded..e90d8d2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
@@ -34,6 +35,7 @@
private final TestParameters parameters;
private final boolean shrinkDesugaredLibrary;
+ private final boolean supportAllCallbacksFromLibrary;
private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
private static final String EXPECTED_RESULT =
@@ -42,16 +44,24 @@
"forEach called",
"action called from java consumer",
"forEach called");
+ private static final String FAILING_EXPECTED_RESULT =
+ StringUtils.lines(
+ "action called from j$ consumer", "forEach called", "action called from java consumer");
private static Path CUSTOM_LIB;
- @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ @Parameters(name = "{0}, shrink: {1}, supportCallbacks: {2}")
public static List<Object[]> data() {
return buildParameters(
- getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ getConversionParametersUpToExcluding(MIN_SUPPORTED),
+ BooleanUtils.values(),
+ BooleanUtils.values());
}
public ConversionIntroduceInterfaceMethodTest(
- TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ TestParameters parameters,
+ boolean shrinkDesugaredLibrary,
+ boolean supportAllCallbacksFromLibrary) {
+ this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
this.parameters = parameters;
}
@@ -75,6 +85,11 @@
.addLibraryClasses(CustomLibClass.class)
.addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .addOptionsModification(
+ opt ->
+ opt.desugaredLibraryConfiguration =
+ configurationWithSupportAllCallbacksFromLibrary(
+ opt, false, parameters, supportAllCallbacksFromLibrary))
.compile()
.inspect(this::assertDoubleForEach)
.inspect(this::assertWrapperMethodsPresent)
@@ -85,11 +100,13 @@
shrinkDesugaredLibrary)
.addRunClasspathFiles(CUSTOM_LIB)
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutput(EXPECTED_RESULT);
+ .apply(
+ r ->
+ r.assertSuccessWithOutput(
+ supportAllCallbacksFromLibrary ? EXPECTED_RESULT : FAILING_EXPECTED_RESULT));
}
private void assertDoubleForEach(CodeInspector inspector) {
- System.out.println(inspector.allClasses().size());
FoundClassSubject myCollection =
inspector.allClasses().stream()
.filter(
@@ -103,7 +120,7 @@
.collect(toSingle());
assertEquals(
"Missing duplicated forEach",
- 2,
+ supportAllCallbacksFromLibrary ? 2 : 1,
IterableUtils.size(
myCollection
.getDexProgramClass()
@@ -133,6 +150,11 @@
.addKeepMainRule(Executor.class)
.addLibraryClasses(CustomLibClass.class)
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .addOptionsModification(
+ opt ->
+ opt.desugaredLibraryConfiguration =
+ configurationWithSupportAllCallbacksFromLibrary(
+ opt, false, parameters, supportAllCallbacksFromLibrary))
.compile()
.inspect(this::assertDoubleForEach)
.inspect(this::assertWrapperMethodsPresent)
@@ -143,7 +165,10 @@
shrinkDesugaredLibrary)
.addRunClasspathFiles(CUSTOM_LIB)
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutput(EXPECTED_RESULT);
+ .apply(
+ r ->
+ r.assertSuccessWithOutput(
+ supportAllCallbacksFromLibrary ? EXPECTED_RESULT : FAILING_EXPECTED_RESULT));
}
static class CustomLibClass {
@@ -197,7 +222,7 @@
@Override
public Iterator<E> iterator() {
- return null;
+ return (Iterator<E>) Collections.singletonList(null).iterator();
}
@NotNull
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
index 1f433d4..812cd34 100644
--- 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
@@ -6,55 +6,84 @@
import static junit.framework.TestCase.assertEquals;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.DexRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
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.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.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class DuplicateAPIDesugaredLibTest extends DesugaredLibraryTestBase {
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+
+ @Parameterized.Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+ }
+
+ public DuplicateAPIDesugaredLibTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
@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.buildDesugaredLibrary(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);
+ for (Boolean supportAllCallbacksFromLibrary : BooleanUtils.values()) {
+ 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.buildDesugaredLibrary(
+ api,
+ opt ->
+ opt.desugaredLibraryConfiguration =
+ configurationWithSupportAllCallbacksFromLibrary(
+ opt, true, parameters, supportAllCallbacksFromLibrary)));
+ 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()), supportAllCallbacksFromLibrary);
+ assertLines2By2Correct(stdOut);
+ }
}
- private void assertDupMethod(CodeInspector inspector) {
+ private void assertDupMethod(CodeInspector inspector, boolean supportAllCallbacksFromLibrary) {
ClassSubject clazz = inspector.clazz("j$.util.concurrent.ConcurrentHashMap");
assertEquals(
- 2,
+ supportAllCallbacksFromLibrary ? 2 : 1,
clazz.virtualMethods().stream().filter(m -> m.getOriginalName().equals("forEach")).count());
}