Generate conversion wrappers in the desugared library.
Bug: 157681341
Bug: 158645207
Change-Id: I558fcb4a53c4ae97cb1a27629f7d0572c41fbb1c
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 4bef8cec..ddca96a 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.jar.CfApplicationWriter;
@@ -118,11 +119,16 @@
// on it.
options.enableLoadStoreOptimization = false;
- DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
+ LazyLoadedDexApplication lazyApp =
+ new ApplicationReader(inputApp, options, timing).read(executor);
+ // Store a direct lookup to determine actual library definitions for wrapper generation.
+ options.desugaredLibraryBootclasspathDefinitions =
+ t -> lazyApp.libraryDefintionFor(t) != null;
+
PrefixRewritingMapper rewritePrefix =
options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
- app = new L8TreePruner(options).prune(app, rewritePrefix);
+ DexApplication app = new L8TreePruner(options).prune(lazyApp, rewritePrefix);
AppInfo appInfo = new AppInfo(app);
AppView<?> appView = AppView.createForL8(appInfo, options, rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index ede2193..ca70ab0 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -74,11 +74,11 @@
this.inputApp = inputApp;
}
- public DexApplication read() throws IOException {
+ public LazyLoadedDexApplication read() throws IOException {
return read((StringResource) null);
}
- public DexApplication read(StringResource proguardMap) throws IOException {
+ public LazyLoadedDexApplication read(StringResource proguardMap) throws IOException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
return read(proguardMap, executor);
@@ -87,20 +87,20 @@
}
}
- public final DexApplication read(ExecutorService executorService) throws IOException {
+ public final LazyLoadedDexApplication read(ExecutorService executorService) throws IOException {
return read(
null, executorService, ProgramClassCollection.defaultConflictResolver(options.reporter));
}
- public final DexApplication read(StringResource proguardMap, ExecutorService executorService)
- throws IOException {
+ public final LazyLoadedDexApplication read(
+ StringResource proguardMap, ExecutorService executorService) throws IOException {
return read(
proguardMap,
executorService,
ProgramClassCollection.defaultConflictResolver(options.reporter));
}
- public final DexApplication read(
+ public final LazyLoadedDexApplication read(
StringResource proguardMap,
ExecutorService executorService,
ProgramClassConflictResolver resolver)
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index f327f21..9eb7caa 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -64,7 +64,14 @@
}
private boolean shouldKeep(DexType type) {
- return namingLens.prefixRewrittenType(type) != null || potentialTypesToKeep.contains(type);
+ return namingLens.prefixRewrittenType(type) != null
+ || potentialTypesToKeep.contains(type)
+ // TODO(b/158632510): This should prefix match on DexString.
+ || type.toDescriptorString()
+ .startsWith(
+ "L"
+ + options.desugaredLibraryConfiguration
+ .getSynthesizedLibraryClassesPackagePrefix());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 9e96ab4..7127794 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -88,6 +88,11 @@
return programClasses.get(type);
}
+ public DexLibraryClass libraryDefintionFor(DexType type) {
+ assert type.isClassType() : "Cannot lookup library definition for type: " + type;
+ return libraryClasses.get(type);
+ }
+
static class AllClasses {
// Mapping of all types to their definitions.
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 96b5de8..1fa68dd 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -4,28 +4,23 @@
package com.android.tools.r8.graph.analysis;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClasspathClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.ArrayList;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
public class DesugaredLibraryConversionWrapperAnalysis extends EnqueuerAnalysis
implements EnqueuerInvokeAnalysis {
private final AppView<?> appView;
private final DesugaredLibraryAPIConverter converter;
- private Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
+ private Map<DexType, DexClasspathClass> synthesizedWrappers = new IdentityHashMap<>();
public DesugaredLibraryConversionWrapperAnalysis(AppView<?> appView) {
this.appView = appView;
@@ -71,34 +66,7 @@
return converter.generateCallbackMethods();
}
- public List<DexProgramClass> generateWrappers() {
- return converter.synthesizeWrappers(synthesizedWrappers);
- }
-
- // Generate a mock classpath class for all vivified types.
- // Types will be available at runtime in the desugared library dex file.
- public List<DexClasspathClass> generateWrappersSuperTypeMock(List<DexProgramClass> wrappers) {
- List<DexClasspathClass> classpathClasses = new ArrayList<>();
- for (DexProgramClass wrapper : wrappers) {
- boolean mockIsInterface = wrapper.interfaces.size() == 1;
- DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
- if (appView.definitionFor(mockType) == null) {
- assert DesugaredLibraryAPIConverter.isVivifiedType(mockType);
- assert wrapper.instanceFields().size() == 1;
- DexType typeToMock = wrapper.instanceFields().get(0).field.type;
- DexClass classToMock = appView.definitionFor(typeToMock);
- assert classToMock != null;
- DexClasspathClass mockedSuperClass =
- converter.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
- classpathClasses.add(mockedSuperClass);
- for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
- // The mock is generated at the end of the enqueuing phase, so we need to manually set the
- // library override.
- assert mockedSuperClass.lookupVirtualMethod(virtualMethod.method) != null;
- virtualMethod.setLibraryMethodOverride(OptionalBool.TRUE);
- }
- }
- }
- return classpathClasses;
+ public void generateWrappers(Consumer<DexClasspathClass> synthesizedCallback) {
+ converter.synthesizeWrappers(synthesizedWrappers, synthesizedCallback);
}
}
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 a91c1aa..277ba09 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
@@ -45,8 +45,8 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
-// TODO(b/134732760): In progress.
// I convert library calls with desugared parameters/return values so they can work normally.
// In the JSON of the desugared library, one can specify conversions between desugared and
// non-desugared types. If no conversion is specified, D8/R8 simply generate wrapper classes around
@@ -287,7 +287,9 @@
}
SortedProgramMethodSet callbacks = generateCallbackMethods();
irConverter.processMethodsConcurrently(callbacks, executorService);
- wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+ }
}
public SortedProgramMethodSet generateCallbackMethods() {
@@ -313,14 +315,10 @@
return allCallbackMethods;
}
- public List<DexProgramClass> synthesizeWrappers(
- Map<DexType, DexProgramClass> synthesizedWrappers) {
- return wrapperSynthesizor.synthesizeWrappers(synthesizedWrappers);
- }
-
- public DexClasspathClass synthesizeClasspathMock(
- DexClass classToMock, DexType mockType, boolean mockIsInterface) {
- return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
+ public void synthesizeWrappers(
+ Map<DexType, DexClasspathClass> synthesizedWrappers,
+ Consumer<DexClasspathClass> synthesizedCallback) {
+ wrapperSynthesizor.synthesizeWrappersForClasspath(synthesizedWrappers, synthesizedCallback);
}
private ProgramMethod generateCallbackMethod(
@@ -351,6 +349,11 @@
}
public void reportInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ // TODO(b/158645207): If wrappers are exactly specified this should fail both for normal
+ // compilation and L8.
+ return;
+ }
DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
appView
.options()
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 a7d5252..60cf4c6 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
@@ -29,7 +29,7 @@
public static final String FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX = "j$/";
- // TODO(b/134732760): should use DexString, DexType, DexMethod or so on when possible.
+ // 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;
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 a659cc6..8896170 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
@@ -107,6 +107,22 @@
}
parseFlags(jsonFlagSet.getAsJsonObject());
}
+ if (libraryCompilation && formatVersion <= 4) {
+ // Read custom conversions from program flags.
+ for (JsonElement jsonFlagSet : jsonConfig.getAsJsonArray("program_flags")) {
+ JsonObject jsonFlagSetObject = jsonFlagSet.getAsJsonObject();
+ int api_level_below_or_equal = jsonFlagSetObject.get("api_level_below_or_equal").getAsInt();
+ if (minAPILevel > api_level_below_or_equal) {
+ continue;
+ }
+ String customConversionKey = "custom_conversion";
+ if (jsonFlagSetObject.has(customConversionKey)) {
+ JsonObject backportedFlags = new JsonObject();
+ backportedFlags.add(customConversionKey, jsonFlagSetObject.get(customConversionKey));
+ parseFlags(backportedFlags);
+ }
+ }
+ }
if (jsonConfig.has("shrinker_config")) {
JsonArray jsonKeepRules = jsonConfig.get("shrinker_config").getAsJsonArray();
List<String> extraKeepRules = new ArrayList<>(jsonKeepRules.size());
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 5fbf3df..c911d6b 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
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
@@ -33,7 +34,6 @@
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -48,6 +48,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
// I am responsible for the generation of wrappers used to call library APIs when desugaring
// libraries. Wrappers can be both ways, wrapping the desugarType as a type, or the type as
@@ -115,7 +117,13 @@
DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
this.appView = appView;
this.factory = appView.dexItemFactory();
- dexWrapperPrefixString = "L" + appView.options().synthesizedClassPrefix + WRAPPER_PREFIX;
+ dexWrapperPrefixString =
+ "L"
+ + appView
+ .options()
+ .desugaredLibraryConfiguration
+ .getSynthesizedLibraryClassesPackagePrefix()
+ + WRAPPER_PREFIX;
dexWrapperPrefixDexString = factory.createString(dexWrapperPrefixString);
this.converter = converter;
this.vivifiedSourceFile = appView.dexItemFactory().createString("vivified");
@@ -126,11 +134,96 @@
}
boolean canGenerateWrapper(DexType type) {
- DexClass dexClass = appView.definitionFor(type);
- if (dexClass == null || dexClass.accessFlags.isFinal()) {
+ return typeWrappers.containsKey(type) || canGenerateWrapper(appView.definitionFor(type));
+ }
+
+ /**
+ * Wrappers can/are generated for all types that appear to support wrapper generation. The
+ * assumptions right now are that the 'type' must be specified in the bootclasspath library, that
+ * means the library component for any build (all of D8, R8 *and* L8). For L8 the type lookup
+ * needs to bypass the usual definitionsFor and look directly in the library on the initial app.
+ *
+ * <p>In addition (or rather before doing the lookup) the class to be wrapped must satisfy several
+ * requirements: its super types can also be wrapped, it has no instance fields, all instance
+ * methods are non-final and public and the types in them are also library defined. If all of that
+ * holds wrappers for the type are created.
+ *
+ * <p>TODO(b/158645207): Consider adding wrappers to the spec and have this be precise.
+ */
+ boolean canGenerateWrapper(DexClass clazz) {
+ if (typeWrappers.containsKey(clazz.type)) {
+ return true;
+ }
+ if (clazz == null
+ || clazz.accessFlags.isFinal()
+ || !clazz.accessFlags.isPublic()
+ || clazz.isAnonymousClass()) {
return false;
}
- return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
+ if (!clazz.isLibraryClass()) {
+ if (!appView.options().isDesugaredLibraryCompilation()) {
+ return false;
+ }
+ // If doing the desugared library compilation, then the class to be wrapped must be defined on
+ // the actual bootclasspath (not the desugared library). The definition lookup must thus
+ // bypass the usual lookup in this case.
+ if (!appView.options().desugaredLibraryBootclasspathDefinitions.test(clazz.type)) {
+ return false;
+ }
+ }
+ if (clazz.superType == null) {
+ return false;
+ }
+ if (clazz.superType != factory.objectType) {
+ if (!canGenerateWrapper(clazz.superType)) {
+ return false;
+ }
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ if (!canGenerateWrapper(iface)) {
+ return false;
+ }
+ }
+ for (DexEncodedField field : clazz.instanceFields()) {
+ return false;
+ }
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ if (method.isFinal() || !method.isPublic()) {
+ return false;
+ }
+ if (isUndefinedOrNonLibraryType(method.proto().returnType)) {
+ return false;
+ }
+ for (DexType param : method.proto().parameters.values) {
+ if (isUndefinedOrNonLibraryType(param)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean isUndefinedOrNonLibraryType(DexType type) {
+ if (type.isPrimitiveType()) {
+ return false;
+ }
+ DexType baseType = type.toBaseType(appView.dexItemFactory());
+ if (!baseType.isClassType()) {
+ return false;
+ }
+ DexClass clazz = appView.definitionFor(baseType);
+ if (clazz == null) {
+ return true;
+ }
+ if (clazz.isLibraryClass()) {
+ return false;
+ }
+ if (!appView.options().isDesugaredLibraryCompilation()) {
+ return true;
+ }
+ // The wrapper referenced type is also considered undefined if not on the bootclasspath.
+ // TODO(b/158718959): Notice use of 'type' vs 'clazz.type' as emulated interfaces are mutated.
+ return !appView.options().desugaredLibraryBootclasspathDefinitions.test(type);
}
DexType getTypeWrapper(DexType type) {
@@ -155,9 +248,10 @@
return wrappers.computeIfAbsent(
type,
t -> {
+ assert canGenerateWrapper(type);
DexType wrapperType = createWrapperType(type, suffix);
assert converter.canGenerateWrappersAndCallbacks()
- || appView.definitionForProgramType(wrapperType) != null
+ || appView.definitionFor(wrapperType).isClasspathClass()
: "Wrapper " + wrapperType + " should have been generated in the enqueuer.";
return wrapperType;
});
@@ -176,10 +270,12 @@
return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
}
- private DexProgramClass generateTypeWrapper(DexClass dexClass, DexType typeWrapperType) {
+ private DexClass generateTypeWrapper(
+ ClassKind classKind, DexClass dexClass, DexType typeWrapperType) {
DexType type = dexClass.type;
DexEncodedField wrapperField = synthesizeWrappedValueEncodedField(typeWrapperType, type);
return synthesizeWrapper(
+ classKind,
vivifiedTypeFor(type),
dexClass,
synthesizeVirtualMethodsForTypeWrapper(dexClass, wrapperField),
@@ -187,12 +283,13 @@
wrapperField);
}
- private DexProgramClass generateVivifiedTypeWrapper(
- DexClass dexClass, DexType vivifiedTypeWrapperType) {
+ private DexClass generateVivifiedTypeWrapper(
+ ClassKind classKind, DexClass dexClass, DexType vivifiedTypeWrapperType) {
DexType type = dexClass.type;
DexEncodedField wrapperField =
synthesizeWrappedValueEncodedField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
return synthesizeWrapper(
+ classKind,
type,
dexClass,
synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass, wrapperField),
@@ -200,7 +297,8 @@
wrapperField);
}
- private DexProgramClass synthesizeWrapper(
+ private DexClass synthesizeWrapper(
+ ClassKind classKind,
DexType wrappingType,
DexClass clazz,
DexEncodedMethod[] virtualMethods,
@@ -210,9 +308,9 @@
DexType superType = isItf ? factory.objectType : wrappingType;
DexTypeList interfaces =
isItf ? new DexTypeList(new DexType[] {wrappingType}) : DexTypeList.empty();
- return new DexProgramClass(
+ return classKind.create(
wrapperField.holder(),
- null,
+ Kind.CF,
new SynthesizedOrigin("Desugared library API Converter", getClass()),
ClassAccessFlags.fromSharedAccessFlags(
Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
@@ -229,8 +327,7 @@
new DexEncodedMethod[] {synthesizeConstructor(wrapperField.field), conversionMethod},
virtualMethods,
factory.getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType,
- Collections.emptyList());
+ DexProgramClass::checksumFromType);
}
private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
@@ -325,51 +422,6 @@
return finalizeWrapperMethods(generatedMethods, finalMethods);
}
- private DexEncodedMethod[] synthesizeVirtualMethodsForClasspathMock(
- DexClass dexClass, DexType mockType) {
- List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
- List<DexEncodedMethod> generatedMethods = new ArrayList<>();
- // Generate only abstract methods for library override detection.
- for (DexEncodedMethod dexEncodedMethod : dexMethods) {
- DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
- assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
- if (!dexEncodedMethod.isFinal()) {
- DexMethod methodToInstall =
- DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
- dexEncodedMethod.method, mockType, appView);
- DexEncodedMethod newDexEncodedMethod =
- newSynthesizedMethod(methodToInstall, dexEncodedMethod, null);
- generatedMethods.add(newDexEncodedMethod);
- }
- }
- return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
- }
-
- DexClasspathClass synthesizeClasspathMock(
- DexClass classToMock, DexType mockType, boolean mockIsInterface) {
- return new DexClasspathClass(
- mockType,
- Kind.CF,
- new SynthesizedOrigin("Desugared library wrapper super class ", getClass()),
- ClassAccessFlags.fromDexAccessFlags(
- Constants.ACC_SYNTHETIC
- | Constants.ACC_PUBLIC
- | (BooleanUtils.intValue(mockIsInterface) * Constants.ACC_INTERFACE)),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- vivifiedSourceFile,
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- synthesizeVirtualMethodsForClasspathMock(classToMock, mockType),
- appView.dexItemFactory().getSkipNameValidationForTesting());
- }
-
private DexEncodedMethod[] finalizeWrapperMethods(
List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
if (finalMethods.isEmpty()) {
@@ -495,33 +547,69 @@
void finalizeWrappersForD8(
DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
throws ExecutionException {
- List<DexProgramClass> synthesizedWrappers = synthesizeWrappers(new IdentityHashMap<>());
+ List<DexProgramClass> synthesizedWrappers = synthesizeWrappers();
registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers);
}
- List<DexProgramClass> synthesizeWrappers(Map<DexType, DexProgramClass> synthesizedWrappers) {
+ private List<DexProgramClass> synthesizeWrappers() {
+ Map<DexType, DexClass> synthesizedWrappers = new IdentityHashMap<>();
List<DexProgramClass> additions = new ArrayList<>();
- // Generating a wrapper may require other wrappers to be generated, iterate until fix point.
- while (synthesizedWrappers.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
+ DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (conf.getCustomConversions().get(clazz.type) == null
+ && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
+ && canGenerateWrapper(clazz)) {
+ getTypeWrapper(clazz.type);
+ }
+ }
+ int total = typeWrappers.size() + vivifiedTypeWrappers.size();
+ generateWrappers(
+ ClassKind.PROGRAM,
+ synthesizedWrappers.keySet(),
+ (type, wrapper) -> {
+ synthesizedWrappers.put(type, wrapper);
+ additions.add(wrapper.asProgramClass());
+ });
+ assert total == typeWrappers.size() + vivifiedTypeWrappers.size() : "unexpected additions";
+ return additions;
+ }
+
+ void synthesizeWrappersForClasspath(
+ Map<DexType, DexClasspathClass> synthesizedWrappers,
+ Consumer<DexClasspathClass> synthesizedCallback) {
+ generateWrappers(
+ ClassKind.CLASSPATH,
+ synthesizedWrappers.keySet(),
+ (type, wrapper) -> {
+ DexClasspathClass classpathWrapper = wrapper.asClasspathClass();
+ synthesizedWrappers.put(type, classpathWrapper);
+ synthesizedCallback.accept(classpathWrapper);
+ });
+ }
+
+ void generateWrappers(
+ ClassKind classKind,
+ Set<DexType> synthesized,
+ BiConsumer<DexType, DexClass> generatedCallback) {
+ while (synthesized.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
for (DexType type : typeWrappers.keySet()) {
DexType typeWrapperType = typeWrappers.get(type);
- if (!synthesizedWrappers.containsKey(typeWrapperType)) {
- DexProgramClass wrapper = generateTypeWrapper(getValidClassToWrap(type), typeWrapperType);
- synthesizedWrappers.put(typeWrapperType, wrapper);
- additions.add(wrapper);
+ if (!synthesized.contains(typeWrapperType)) {
+ DexClass wrapper =
+ generateTypeWrapper(classKind, getValidClassToWrap(type), typeWrapperType);
+ generatedCallback.accept(typeWrapperType, wrapper);
}
}
for (DexType type : vivifiedTypeWrappers.keySet()) {
DexType vivifiedTypeWrapperType = vivifiedTypeWrappers.get(type);
- if (!synthesizedWrappers.containsKey(vivifiedTypeWrapperType)) {
- DexProgramClass wrapper =
- generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType);
- synthesizedWrappers.put(vivifiedTypeWrapperType, wrapper);
- additions.add(wrapper);
+ if (!synthesized.contains(vivifiedTypeWrapperType)) {
+ DexClass wrapper =
+ generateVivifiedTypeWrapper(
+ classKind, getValidClassToWrap(type), vivifiedTypeWrapperType);
+ generatedCallback.accept(vivifiedTypeWrapperType, wrapper);
}
}
}
- return additions;
}
private void registerAndProcessWrappers(
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 76c9e4c..e432e69 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2725,7 +2725,10 @@
Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
boolean isEmpty() {
- boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
+ boolean empty =
+ syntheticInstantiations.isEmpty()
+ && liveMethods.isEmpty()
+ && syntheticClasspathClasses.isEmpty();
assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
return empty;
}
@@ -2787,6 +2790,7 @@
enqueuer.markMethodAsTargeted(liveMethod, fakeReason);
enqueuer.enqueueMarkMethodLiveAction(liveMethod, fakeReason);
}
+ enqueuer.liveNonProgramTypes.addAll(syntheticClasspathClasses.values());
}
}
@@ -3099,21 +3103,8 @@
ProgramMethodSet callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
callbacks.forEach(additions::addLiveMethod);
- // Generate the wrappers.
- List<DexProgramClass> wrappers = desugaredLibraryWrapperAnalysis.generateWrappers();
- for (DexProgramClass wrapper : wrappers) {
- additions.addInstantiatedClass(wrapper, null, false);
- // Mark all methods on the wrapper as live and targeted.
- for (DexEncodedMethod method : wrapper.methods()) {
- additions.addLiveMethod(new ProgramMethod(wrapper, method));
- }
- }
-
- // Add all vivified types as classpath classes.
- // They will be available at runtime in the desugared library dex file.
- desugaredLibraryWrapperAnalysis
- .generateWrappersSuperTypeMock(wrappers)
- .forEach(additions::addClasspathClass);
+ // Generate wrappers on classpath so types are defined.
+ desugaredLibraryWrapperAnalysis.generateWrappers(additions::addClasspathClass);
}
private void rewriteLambdaCallSites(
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 00853e0..8599b02 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -419,7 +419,8 @@
if (outputMode == OutputMode.DexIndexed) {
DexIndexedConsumer.ArchiveConsumer.writeResources(
archive, getDexProgramResourcesForTesting(), getDataEntryResourcesForTesting());
- } else if (outputMode == OutputMode.DexFilePerClassFile) {
+ } else if (outputMode == OutputMode.DexFilePerClassFile
+ || outputMode == OutputMode.DexFilePerClass) {
List<ProgramResource> resources = getDexProgramResourcesForTesting();
DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
archive, resources, programResourcesMainDescriptor);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 35e4614..f83359a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -680,6 +680,9 @@
public DesugaredLibraryConfiguration desugaredLibraryConfiguration =
DesugaredLibraryConfiguration.empty();
+ // Method to lookup library definitions.
+ public Predicate<DexType> desugaredLibraryBootclasspathDefinitions;
+
public boolean relocatorCompilation = false;
// If null, no keep rules are recorded.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibaryChecksumsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibaryChecksumsTest.java
new file mode 100644
index 0000000..3e819e2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibaryChecksumsTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.L8;
+import com.android.tools.r8.L8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibaryChecksumsTest extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public DesugaredLibaryChecksumsTest(TestParameters parameters) {
+ assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path out = temp.newFolder().toPath().resolve("out.jar");
+ L8.run(
+ L8Command.builder()
+ .setIncludeClassesChecksum(true)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+ .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+ .setMode(CompilationMode.DEBUG)
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .setMinApiLevel(AndroidApiLevel.B.getLevel())
+ .setOutput(out, OutputMode.DexIndexed)
+ .build());
+
+ try {
+ CodeInspector inspector = new CodeInspector(out);
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ assertTrue(clazz.getDexProgramClass().getChecksum() > 0);
+ }
+ } catch (CompilationError e) {
+ // TODO(b/158746302): Desugared library should support checksums.
+ // also, the failure should have occured in the L8.run above!
+ assertThat(e.getMessage(), containsString("has no checksum"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index 579b6f6..ad8e74c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -31,7 +31,7 @@
private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
private static final String EXPECTED_RESULT =
StringUtils.lines(
- "[5, 6, 7]", "$r8$wrapper$java$util$stream$IntStream$-V-WRP", "IntSummaryStatistics");
+ "[5, 6, 7]", "j$.$r8$wrapper$java$util$stream$IntStream$-V-WRP", "IntSummaryStatistics");
@Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
public static List<Object[]> data() {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
index 4581211..382f55d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ClockAPIConversionTest.java
@@ -57,6 +57,7 @@
.addLibraryClasses(CustomLibClass.class)
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
+ .assertNoMessages()
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 3d1b999..106dd72 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-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;
@@ -21,8 +20,6 @@
import java.util.function.IntSupplier;
import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
-import org.junit.Assert;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -101,43 +98,6 @@
.assertSuccessWithOutput(EXPECTED_RESULT);
}
- @Test
- public void testWrapperWithChecksum() throws Exception {
- Assume.assumeTrue(
- shrinkDesugaredLibrary && parameters.getApiLevel().getLevel() <= MIN_SUPPORTED.getLevel());
- testForD8()
- .addProgramClasses(
- Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
- .addLibraryClasses(CustomLibClass.class)
- .setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
- .setIncludeClassesChecksum(true) // Compilation fails if some classes are missing checksum.
- .compile()
- .inspect(
- inspector -> {
- Assert.assertEquals(
- 9,
- inspector.allClasses().stream()
- .filter(
- clazz ->
- clazz
- .getFinalName()
- .contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX))
- .count());
- Assert.assertEquals(
- 9,
- inspector.allClasses().stream()
- .filter(
- clazz ->
- clazz
- .getFinalName()
- .contains(
- DesugaredLibraryWrapperSynthesizer
- .VIVIFIED_TYPE_WRAPPER_SUFFIX))
- .count());
- });
- }
-
static class Executor {
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
index 9a29671..c95420c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestParameters;
@@ -17,12 +16,10 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
+import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
-import java.util.Set;
+import java.util.stream.Stream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -57,21 +54,28 @@
}
@Test
- public void testWrapperMerge() throws Exception {
+ public void testNoWrappers() throws Exception {
assumeTrue(parameters.isDexRuntime());
- // Multiple wrapper classes have to be merged here.
+ // No wrappers are made during program compilation.
Path path1 = compileWithCoreLibraryDesugaring(MyArrays1.class);
Path path2 = compileWithCoreLibraryDesugaring(MyArrays2.class);
testForD8()
- .addProgramFiles(path1, path2)
.addProgramClasses(TestClass.class)
.addAndroidBuildVersion()
.enableCoreLibraryDesugaring(parameters.getApiLevel())
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::assertWrappers)
- .inspect(this::assertNoDuplicates)
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .inspect(this::assertNoWrappers)
+ .apply(
+ b -> {
+ if (!hasNativeIntUnaryOperator()) {
+ Path coreLib = buildDesugaredLibrary(parameters.getApiLevel());
+ assertCoreLibContainsWrappers(coreLib);
+ b.addRunClasspathFiles(coreLib);
+ }
+ })
+ // The previous compilations are appended to the classpath (no merge).
+ .addRunClasspathFiles(path1, path2)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
}
@@ -82,34 +86,32 @@
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel())
.compile()
- .inspect(this::assertWrappers)
+ .inspect(this::assertNoWrappers)
.writeToZip();
}
- private void assertNoDuplicates(CodeInspector inspector) {
- Object2ReferenceMap<String, Set<FoundClassSubject>> map = new Object2ReferenceOpenHashMap<>();
- for (FoundClassSubject clazz : inspector.allClasses()) {
- map.computeIfAbsent(clazz.getFinalName(), k -> Sets.newIdentityHashSet()).add(clazz);
- }
- for (Set<FoundClassSubject> duplicates : map.values()) {
- if (duplicates.size() > 1) {
- fail("Unexpected duplicates: " + duplicates);
- }
- }
- }
-
private boolean hasNativeIntUnaryOperator() {
return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
}
- private void assertWrappers(CodeInspector inspector) {
+ private void assertCoreLibContainsWrappers(Path coreLib) throws IOException {
+ CodeInspector inspector = new CodeInspector(coreLib);
+ // TODO(b/158645207): Consider having wrappers in the spec and avoiding this issue.
assertEquals(
- hasNativeIntUnaryOperator() ? 0 : 2,
- inspector.allClasses().stream()
- .filter(
- c ->
- c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))
- .count());
+ "Number of generated wrappers changes. Manually verify validity of the change.",
+ 144,
+ getWrappers(inspector).count());
+ }
+
+ private void assertNoWrappers(CodeInspector inspector) {
+ Stream<FoundClassSubject> wrappers = getWrappers(inspector);
+ assertEquals(0, wrappers.count());
+ }
+
+ private Stream<FoundClassSubject> getWrappers(CodeInspector inspector) {
+ return inspector.allClasses().stream()
+ .filter(
+ c -> c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX));
}
static class MyArrays1 {