Version 2.1.44
Cherry-pick: "Report unsupported based on desugared library version."
CL: https://r8-review.googlesource.com/c/r8/+/52289
Cherry-pick: "Backwards compatible desugared library configuration."
CL: https://r8-review.googlesource.com/c/r8/+/52241
Cherry-pick: "Update test expectation after error message change."
CL: https://r8-review.googlesource.com/c/r8/+/52220
Cherry-pick: "Specify wrapper types in the desugared library configuration."
CL: https://r8-review.googlesource.com/c/r8/+/52101
Cherry-pick: "Generate conversion wrappers in the desugared library."
CL: https://r8-review.googlesource.com/c/r8/+/52042
Cherry-pick: "Test wrapper merging on all VMs and API levels."
CL: https://r8-review.googlesource.com/c/r8/+/51859
Bug: 157681341
Bug: 158645207
Change-Id: If2c471396179dfd99e18b2f36b8a513cbae12ac4
diff --git a/.gitignore b/.gitignore
index 0629bac..40cf737 100644
--- a/.gitignore
+++ b/.gitignore
@@ -135,6 +135,8 @@
third_party/protobuf-lite/
third_party/r8
third_party/r8.tar.gz
+third_party/r8-releases/2.0.74
+third_party/r8-releases/2.0.74.tar.gz
third_party/r8mappings
third_party/r8mappings.tar.gz
third_party/remapper
diff --git a/build.gradle b/build.gradle
index 338df88..ffc0819 100644
--- a/build.gradle
+++ b/build.gradle
@@ -334,6 +334,7 @@
"proguard/proguard5.2.1",
"proguard/proguard6.0.1",
"r8",
+ "r8-releases/2.0.74",
"r8mappings",
"tachiyomi"
],
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 7d5b31e..dcf33f1 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -69,7 +69,7 @@
private final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
- private GenerateLintFiles(String desugarConfigurationPath, String outputDirectory) {
+ public GenerateLintFiles(String desugarConfigurationPath, String outputDirectory) {
this.desugaredLibraryConfiguration =
readDesugaredLibraryConfiguration(desugarConfigurationPath);
this.outputDirectory =
@@ -356,20 +356,24 @@
apiLevel >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel();
apiLevel--) {
System.out.println("Generating lint files for compile API " + apiLevel);
- generateLintFiles(
- AndroidApiLevel.getAndroidApiLevel(apiLevel),
- minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B,
- (minApiLevel, method) -> {
- assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B;
- if (minApiLevel == AndroidApiLevel.L) {
- return true;
- }
- assert minApiLevel == AndroidApiLevel.B;
- return !parallelMethods.contains(method.method);
- });
+ run(apiLevel);
}
}
+ public void run(int apiLevel) throws Exception {
+ generateLintFiles(
+ AndroidApiLevel.getAndroidApiLevel(apiLevel),
+ minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B,
+ (minApiLevel, method) -> {
+ assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B;
+ if (minApiLevel == AndroidApiLevel.L) {
+ return true;
+ }
+ assert minApiLevel == AndroidApiLevel.B;
+ return !parallelMethods.contains(method.method);
+ });
+ }
+
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.out.println("Usage: GenerateLineFiles <desuage configuration> <output directory>");
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 4bef8cec..4b35d17 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,13 @@
// on it.
options.enableLoadStoreOptimization = false;
- DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
+ LazyLoadedDexApplication lazyApp =
+ new ApplicationReader(inputApp, options, timing).read(executor);
+
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/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7093d91..84eb5f4 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.1.43";
+ public static final String LABEL = "2.1.44";
private Version() {
}
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 88108f9..c0aea76 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
@@ -44,8 +44,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
@@ -286,7 +286,9 @@
}
SortedProgramMethodSet callbacks = generateCallbackMethods();
irConverter.processMethodsConcurrently(callbacks, executorService);
- wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ wrapperSynthesizor.finalizeWrappersForL8(builder, irConverter, executorService);
+ }
}
public SortedProgramMethodSet generateCallbackMethods() {
@@ -312,14 +314,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,20 +349,22 @@
public void reportInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
- appView
- .options()
- .reporter
- .info(
- new StringDiagnostic(
- "Invoke to "
- + invokedMethod.holder
- + "#"
- + invokedMethod.name
- + " may not work correctly at runtime (Cannot convert "
- + debugString
- + "type "
- + desugaredType
- + ")."));
+ StringDiagnostic diagnostic =
+ new StringDiagnostic(
+ "Invoke to "
+ + invokedMethod.holder
+ + "#"
+ + invokedMethod.name
+ + " may not work correctly at runtime (Cannot convert "
+ + debugString
+ + "type "
+ + desugaredType
+ + ").");
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ throw appView.options().reporter.fatalError(diagnostic);
+ } else {
+ appView.options().reporter.info(diagnostic);
+ }
}
public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
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 1590484..5414791 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
@@ -11,24 +11,32 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
public class DesugaredLibraryConfiguration {
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;
@@ -40,9 +48,10 @@
private final Map<DexType, DexType> customConversions;
private final List<Pair<DexType, DexString>> dontRewriteInvocation;
private final List<String> extraKeepRules;
+ private final Set<DexType> wrapperConversions;
- public static Builder builder(DexItemFactory dexItemFactory) {
- return new Builder(dexItemFactory);
+ public static Builder builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
+ return new Builder(dexItemFactory, reporter, origin);
}
public static DesugaredLibraryConfiguration withOnlyRewritePrefixForTesting(
@@ -57,6 +66,7 @@
ImmutableMap.of(),
ImmutableMap.of(),
ImmutableMap.of(),
+ ImmutableSet.of(),
ImmutableList.of(),
ImmutableList.of());
}
@@ -72,11 +82,12 @@
ImmutableMap.of(),
ImmutableMap.of(),
ImmutableMap.of(),
+ ImmutableSet.of(),
ImmutableList.of(),
ImmutableList.of());
}
- public DesugaredLibraryConfiguration(
+ private DesugaredLibraryConfiguration(
AndroidApiLevel requiredCompilationAPILevel,
boolean libraryCompilation,
String packagePrefix,
@@ -86,6 +97,7 @@
Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
Map<DexType, DexType> backportCoreLibraryMember,
Map<DexType, DexType> customConversions,
+ Set<DexType> wrapperConversions,
List<Pair<DexType, DexString>> dontRewriteInvocation,
List<String> extraKeepRules) {
this.requiredCompilationAPILevel = requiredCompilationAPILevel;
@@ -97,6 +109,7 @@
this.retargetCoreLibMember = retargetCoreLibMember;
this.backportCoreLibraryMember = backportCoreLibraryMember;
this.customConversions = customConversions;
+ this.wrapperConversions = wrapperConversions;
this.dontRewriteInvocation = dontRewriteInvocation;
this.extraKeepRules = extraKeepRules;
}
@@ -157,6 +170,10 @@
return customConversions;
}
+ public Set<DexType> getWrapperConversions() {
+ return wrapperConversions;
+ }
+
public List<Pair<DexType, DexString>> getDontRewriteInvocation() {
return dontRewriteInvocation;
}
@@ -168,6 +185,8 @@
public static class Builder {
private final DexItemFactory factory;
+ private final Reporter reporter;
+ private final Origin origin;
private AndroidApiLevel requiredCompilationAPILevel;
private boolean libraryCompilation = false;
@@ -175,15 +194,34 @@
FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX;
private String identifier;
private Map<String, String> rewritePrefix = new HashMap<>();
- private Map<DexType, DexType> emulateLibraryInterface = new HashMap<>();
+ private Map<DexType, DexType> emulateLibraryInterface = new IdentityHashMap<>();
private Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = new IdentityHashMap<>();
- private Map<DexType, DexType> backportCoreLibraryMember = new HashMap<>();
- private Map<DexType, DexType> customConversions = new HashMap<>();
+ private Map<DexType, DexType> backportCoreLibraryMember = new IdentityHashMap<>();
+ private Map<DexType, DexType> customConversions = new IdentityHashMap<>();
+ private Set<DexType> wrapperConversions = Sets.newIdentityHashSet();
private List<Pair<DexType, DexString>> dontRewriteInvocation = new ArrayList<>();
private List<String> extraKeepRules = Collections.emptyList();
- public Builder(DexItemFactory dexItemFactory) {
+ private Builder(DexItemFactory dexItemFactory, Reporter reporter, Origin origin) {
this.factory = dexItemFactory;
+ this.reporter = reporter;
+ this.origin = origin;
+ }
+
+ // Utility to set values. Currently assumes the key is fresh.
+ private <K, V> void put(Map<K, V> map, K key, V value, String desc) {
+ if (map.containsKey(key)) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Invalid desugared library configuration. "
+ + " Duplicate assignment of key: '"
+ + key
+ + "' in sections for '"
+ + desc
+ + "'",
+ origin));
+ }
+ map.put(key, value);
}
public Builder setSynthesizedLibraryClassesPackagePrefix(String prefix) {
@@ -217,7 +255,11 @@
}
public Builder putRewritePrefix(String prefix, String rewrittenPrefix) {
- rewritePrefix.put(prefix, rewrittenPrefix);
+ put(
+ rewritePrefix,
+ prefix,
+ rewrittenPrefix,
+ DesugaredLibraryConfigurationParser.REWRITE_PREFIX_KEY);
return this;
}
@@ -225,14 +267,28 @@
String emulateLibraryItf, String rewrittenEmulateLibraryItf) {
DexType interfaceType = stringClassToDexType(emulateLibraryItf);
DexType rewrittenType = stringClassToDexType(rewrittenEmulateLibraryItf);
- emulateLibraryInterface.put(interfaceType, rewrittenType);
+ put(
+ emulateLibraryInterface,
+ interfaceType,
+ rewrittenType,
+ DesugaredLibraryConfigurationParser.EMULATE_INTERFACE_KEY);
return this;
}
public Builder putCustomConversion(String type, String conversionHolder) {
DexType dexType = stringClassToDexType(type);
DexType conversionType = stringClassToDexType(conversionHolder);
- customConversions.put(dexType, conversionType);
+ put(
+ customConversions,
+ dexType,
+ conversionType,
+ DesugaredLibraryConfigurationParser.CUSTOM_CONVERSION_KEY);
+ return this;
+ }
+
+ public Builder addWrapperConversion(String type) {
+ DexType dexType = stringClassToDexType(type);
+ wrapperConversions.add(dexType);
return this;
}
@@ -244,14 +300,22 @@
DexType originalType = stringClassToDexType(retarget.substring(0, index));
DexType finalType = stringClassToDexType(rewrittenRetarget);
assert !typeMap.containsKey(originalType);
- typeMap.put(originalType, finalType);
+ put(
+ typeMap,
+ originalType,
+ finalType,
+ DesugaredLibraryConfigurationParser.RETARGET_LIB_MEMBER_KEY);
return this;
}
public Builder putBackportCoreLibraryMember(String backport, String rewrittenBackport) {
DexType backportType = stringClassToDexType(backport);
DexType rewrittenBackportType = stringClassToDexType(rewrittenBackport);
- backportCoreLibraryMember.put(backportType, rewrittenBackportType);
+ put(
+ backportCoreLibraryMember,
+ backportType,
+ rewrittenBackportType,
+ DesugaredLibraryConfigurationParser.BACKPORT_KEY);
return this;
}
@@ -278,6 +342,7 @@
}
public DesugaredLibraryConfiguration build() {
+ validate();
return new DesugaredLibraryConfiguration(
requiredCompilationAPILevel,
libraryCompilation,
@@ -288,8 +353,22 @@
ImmutableMap.copyOf(retargetCoreLibMember),
ImmutableMap.copyOf(backportCoreLibraryMember),
ImmutableMap.copyOf(customConversions),
+ ImmutableSet.copyOf(wrapperConversions),
ImmutableList.copyOf(dontRewriteInvocation),
ImmutableList.copyOf(extraKeepRules));
}
+
+ private void validate() {
+ SetView<DexType> dups = Sets.intersection(customConversions.keySet(), wrapperConversions);
+ if (!dups.isEmpty()) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Invalid desugared library configuration. "
+ + "Duplicate types in custom conversions and wrapper conversions: "
+ + String.join(
+ ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
+ 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 a659cc6..d687d19 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
@@ -6,8 +6,11 @@
import com.android.tools.r8.StringResource;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SemanticVersion;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@@ -19,142 +22,184 @@
public class DesugaredLibraryConfigurationParser {
- private static final int MAX_SUPPORTED_VERSION = 4;
+ public static final int MAX_SUPPORTED_VERSION = 4;
+ public static final SemanticVersion MIN_SUPPORTED_VERSION = new SemanticVersion(1, 0, 9);
- private final DesugaredLibraryConfiguration.Builder configurationBuilder;
+ static final String CONFIGURATION_FORMAT_VERSION_KEY = "configuration_format_version";
+ static final String VERSION_KEY = "version";
+ static final String GROUP_ID_KEY = "group_id";
+ static final String ARTIFACT_ID_KEY = "artifact_id";
+ static final String REQUIRED_COMPILATION_API_LEVEL_KEY = "required_compilation_api_level";
+ static final String SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY =
+ "synthesized_library_classes_package_prefix";
+
+ static final String COMMON_FLAGS_KEY = "common_flags";
+ static final String LIBRARY_FLAGS_KEY = "library_flags";
+ static final String PROGRAM_FLAGS_KEY = "program_flags";
+
+ static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
+ static final String WRAPPER_CONVERSION_KEY = "wrapper_conversion";
+ static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
+ static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
+ static final String RETARGET_LIB_MEMBER_KEY = "retarget_lib_member";
+ static final String EMULATE_INTERFACE_KEY = "emulate_interface";
+ static final String DONT_REWRITE_KEY = "dont_rewrite";
+ static final String BACKPORT_KEY = "backport";
+ static final String SHRINKER_CONFIG_KEY = "shrinker_config";
+
+ private final DexItemFactory dexItemFactory;
private final Reporter reporter;
private final boolean libraryCompilation;
private final int minAPILevel;
+ private DesugaredLibraryConfiguration.Builder configurationBuilder = null;
+ private Origin origin;
+
public DesugaredLibraryConfigurationParser(
DexItemFactory dexItemFactory,
Reporter reporter,
boolean libraryCompilation,
int minAPILevel) {
+ this.dexItemFactory = dexItemFactory;
this.reporter = reporter;
- this.configurationBuilder = DesugaredLibraryConfiguration.builder(dexItemFactory);
this.minAPILevel = minAPILevel;
this.libraryCompilation = libraryCompilation;
+ }
+
+ private JsonElement required(JsonObject json, String key) {
+ if (!json.has(key)) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Invalid desugared library configuration. Expected required key '" + key + "'",
+ origin));
+ }
+ return json.get(key);
+ }
+
+ public DesugaredLibraryConfiguration parse(StringResource stringResource) {
+ origin = stringResource.getOrigin();
+ assert origin != null;
+ configurationBuilder = DesugaredLibraryConfiguration.builder(dexItemFactory, reporter, origin);
if (libraryCompilation) {
configurationBuilder.setLibraryCompilation();
} else {
configurationBuilder.setProgramCompilation();
}
- }
-
- public DesugaredLibraryConfiguration parse(StringResource stringResource) {
- String jsonConfigString;
+ JsonObject jsonConfig;
try {
- jsonConfigString = stringResource.getString();
+ String jsonConfigString = stringResource.getString();
+ JsonParser parser = new JsonParser();
+ jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
} catch (Exception e) {
- throw reporter.fatalError(
- new StringDiagnostic("Cannot access desugared library configuration."));
+ throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
}
- JsonParser parser = new JsonParser();
- JsonObject jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
- int formatVersion = jsonConfig.get("configuration_format_version").getAsInt();
+ JsonElement formatVersionElement = required(jsonConfig, CONFIGURATION_FORMAT_VERSION_KEY);
+ int formatVersion = formatVersionElement.getAsInt();
if (formatVersion > MAX_SUPPORTED_VERSION) {
throw reporter.fatalError(
new StringDiagnostic(
"Unsupported desugared library configuration version, please upgrade the D8/R8"
- + " compiler."));
- }
- if (formatVersion == 1) {
- reporter.warning(
- new StringDiagnostic(
- "You are using an experimental version of the desugared library configuration, "
- + "distributed only in the early canary versions. Please update for "
- + "production releases and to fix this warning."));
+ + " compiler.",
+ origin));
}
- String version = jsonConfig.get("version").getAsString();
- String groupID;
- String artifactID;
- if (formatVersion < 4) {
- groupID = "com.tools.android";
- artifactID = "desugar_jdk_libs";
- } else {
- groupID = jsonConfig.get("group_id").getAsString();
- artifactID = jsonConfig.get("artifact_id").getAsString();
+ String version = required(jsonConfig, VERSION_KEY).getAsString();
+ SemanticVersion semanticVersion = SemanticVersion.parse(version);
+ if (!semanticVersion.isNewerOrEqual(MIN_SUPPORTED_VERSION)) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Unsupported desugared library version: "
+ + version
+ + ", please upgrade the desugared library to at least version "
+ + MIN_SUPPORTED_VERSION
+ + ".",
+ origin));
}
+
+ String groupID = required(jsonConfig, GROUP_ID_KEY).getAsString();
+ String artifactID = required(jsonConfig, ARTIFACT_ID_KEY).getAsString();
String identifier = String.join(":", groupID, artifactID, version);
configurationBuilder.setDesugaredLibraryIdentifier(identifier);
+ configurationBuilder.setSynthesizedLibraryClassesPackagePrefix(
+ required(jsonConfig, SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY).getAsString());
- if (jsonConfig.has("synthesized_library_classes_package_prefix")) {
- configurationBuilder.setSynthesizedLibraryClassesPackagePrefix(
- jsonConfig.get("synthesized_library_classes_package_prefix").getAsString());
- } else {
- reporter.warning(
- new StringDiagnostic(
- "Missing package_prefix, falling back to "
- + DesugaredLibraryConfiguration.FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX
- + " prefix, update desugared library configuration."));
- }
int required_compilation_api_level =
- jsonConfig.get("required_compilation_api_level").getAsInt();
+ required(jsonConfig, REQUIRED_COMPILATION_API_LEVEL_KEY).getAsInt();
configurationBuilder.setRequiredCompilationAPILevel(
AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level));
- JsonArray jsonFlags =
- libraryCompilation
- ? jsonConfig.getAsJsonArray("library_flags")
- : jsonConfig.getAsJsonArray("program_flags");
- for (JsonElement jsonFlagSet : jsonFlags) {
- int api_level_below_or_equal =
- jsonFlagSet.getAsJsonObject().get("api_level_below_or_equal").getAsInt();
- if (minAPILevel > api_level_below_or_equal) {
- continue;
- }
- parseFlags(jsonFlagSet.getAsJsonObject());
- }
- if (jsonConfig.has("shrinker_config")) {
- JsonArray jsonKeepRules = jsonConfig.get("shrinker_config").getAsJsonArray();
+ JsonElement commonFlags = required(jsonConfig, COMMON_FLAGS_KEY);
+ JsonElement libraryFlags = required(jsonConfig, LIBRARY_FLAGS_KEY);
+ JsonElement programFlags = required(jsonConfig, PROGRAM_FLAGS_KEY);
+ parseFlagsList(commonFlags.getAsJsonArray());
+ parseFlagsList(
+ libraryCompilation ? libraryFlags.getAsJsonArray() : programFlags.getAsJsonArray());
+ if (jsonConfig.has(SHRINKER_CONFIG_KEY)) {
+ JsonArray jsonKeepRules = jsonConfig.get(SHRINKER_CONFIG_KEY).getAsJsonArray();
List<String> extraKeepRules = new ArrayList<>(jsonKeepRules.size());
for (JsonElement keepRule : jsonKeepRules) {
extraKeepRules.add(keepRule.getAsString());
}
configurationBuilder.setExtraKeepRules(extraKeepRules);
}
- return configurationBuilder.build();
+ DesugaredLibraryConfiguration config = configurationBuilder.build();
+ configurationBuilder = null;
+ origin = null;
+ return config;
+ }
+
+ private void parseFlagsList(JsonArray jsonFlags) {
+ for (JsonElement jsonFlagSet : jsonFlags) {
+ JsonObject flag = jsonFlagSet.getAsJsonObject();
+ int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+ if (minAPILevel <= api_level_below_or_equal) {
+ parseFlags(flag);
+ }
+ }
}
private void parseFlags(JsonObject jsonFlagSet) {
- if (jsonFlagSet.has("rewrite_prefix")) {
+ if (jsonFlagSet.has(REWRITE_PREFIX_KEY)) {
for (Map.Entry<String, JsonElement> rewritePrefix :
- jsonFlagSet.get("rewrite_prefix").getAsJsonObject().entrySet()) {
+ jsonFlagSet.get(REWRITE_PREFIX_KEY).getAsJsonObject().entrySet()) {
configurationBuilder.putRewritePrefix(
rewritePrefix.getKey(), rewritePrefix.getValue().getAsString());
}
}
- if (jsonFlagSet.has("retarget_lib_member")) {
+ if (jsonFlagSet.has(RETARGET_LIB_MEMBER_KEY)) {
for (Map.Entry<String, JsonElement> retarget :
- jsonFlagSet.get("retarget_lib_member").getAsJsonObject().entrySet()) {
+ jsonFlagSet.get(RETARGET_LIB_MEMBER_KEY).getAsJsonObject().entrySet()) {
configurationBuilder.putRetargetCoreLibMember(
retarget.getKey(), retarget.getValue().getAsString());
}
}
- if (jsonFlagSet.has("backport")) {
+ if (jsonFlagSet.has(BACKPORT_KEY)) {
for (Map.Entry<String, JsonElement> backport :
- jsonFlagSet.get("backport").getAsJsonObject().entrySet()) {
+ jsonFlagSet.get(BACKPORT_KEY).getAsJsonObject().entrySet()) {
configurationBuilder.putBackportCoreLibraryMember(
backport.getKey(), backport.getValue().getAsString());
}
}
- if (jsonFlagSet.has("emulate_interface")) {
+ if (jsonFlagSet.has(EMULATE_INTERFACE_KEY)) {
for (Map.Entry<String, JsonElement> itf :
- jsonFlagSet.get("emulate_interface").getAsJsonObject().entrySet()) {
+ jsonFlagSet.get(EMULATE_INTERFACE_KEY).getAsJsonObject().entrySet()) {
configurationBuilder.putEmulateLibraryInterface(itf.getKey(), itf.getValue().getAsString());
}
}
- if (jsonFlagSet.has("custom_conversion")) {
+ if (jsonFlagSet.has(CUSTOM_CONVERSION_KEY)) {
for (Map.Entry<String, JsonElement> conversion :
- jsonFlagSet.get("custom_conversion").getAsJsonObject().entrySet()) {
+ jsonFlagSet.get(CUSTOM_CONVERSION_KEY).getAsJsonObject().entrySet()) {
configurationBuilder.putCustomConversion(
conversion.getKey(), conversion.getValue().getAsString());
}
}
- if (jsonFlagSet.has("dont_rewrite")) {
- JsonArray dontRewrite = jsonFlagSet.get("dont_rewrite").getAsJsonArray();
+ if (jsonFlagSet.has(WRAPPER_CONVERSION_KEY)) {
+ for (JsonElement wrapper : jsonFlagSet.get(WRAPPER_CONVERSION_KEY).getAsJsonArray()) {
+ configurationBuilder.addWrapperConversion(wrapper.getAsString());
+ }
+ }
+ if (jsonFlagSet.has(DONT_REWRITE_KEY)) {
+ JsonArray dontRewrite = jsonFlagSet.get(DONT_REWRITE_KEY).getAsJsonArray();
for (JsonElement rewrite : dontRewrite) {
configurationBuilder.addDontRewriteInvocation(rewrite.getAsString());
}
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..23a6465 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
@@ -110,15 +112,19 @@
private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
private final DexItemFactory factory;
private final DesugaredLibraryAPIConverter converter;
- private final DexString vivifiedSourceFile;
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");
}
boolean hasSynthesized(DexType type) {
@@ -126,11 +132,7 @@
}
boolean canGenerateWrapper(DexType type) {
- DexClass dexClass = appView.definitionFor(type);
- if (dexClass == null || dexClass.accessFlags.isFinal()) {
- return false;
- }
- return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
+ return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
}
DexType getTypeWrapper(DexType type) {
@@ -155,9 +157,10 @@
return wrappers.computeIfAbsent(
type,
t -> {
+ assert canGenerateWrapper(type) : 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 +179,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 +192,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 +206,8 @@
wrapperField);
}
- private DexProgramClass synthesizeWrapper(
+ private DexClass synthesizeWrapper(
+ ClassKind classKind,
DexType wrappingType,
DexClass clazz,
DexEncodedMethod[] virtualMethods,
@@ -210,9 +217,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 +236,7 @@
new DexEncodedMethod[] {synthesizeConstructor(wrapperField.field), conversionMethod},
virtualMethods,
factory.getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType,
- Collections.emptyList());
+ DexProgramClass::checksumFromType);
}
private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
@@ -325,51 +331,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()) {
@@ -492,36 +453,69 @@
true);
}
- void finalizeWrappersForD8(
+ void finalizeWrappersForL8(
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() {
+ DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
+ for (DexType type : conf.getWrapperConversions()) {
+ assert !conf.getCustomConversions().containsKey(type);
+ getTypeWrapper(type);
+ }
+ 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()) {
+ 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);
+ });
+ }
+
+ private 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 2bdfa26..ff06397 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2687,7 +2687,10 @@
Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
boolean isEmpty() {
- boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
+ boolean empty =
+ syntheticInstantiations.isEmpty()
+ && liveMethods.isEmpty()
+ && syntheticClasspathClasses.isEmpty();
assert !empty || (pinnedMethods.isEmpty() && mainDexTypes.isEmpty());
return empty;
}
@@ -2747,6 +2750,7 @@
enqueuer.markMethodAsTargeted(liveMethod, fakeReason);
enqueuer.enqueueMarkMethodLiveAction(liveMethod, fakeReason);
}
+ enqueuer.liveNonProgramTypes.addAll(syntheticClasspathClasses.values());
}
}
@@ -3059,21 +3063,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/SemanticVersion.java b/src/main/java/com/android/tools/r8/utils/SemanticVersion.java
new file mode 100644
index 0000000..977073d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SemanticVersion.java
@@ -0,0 +1,84 @@
+// 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.utils;
+
+import java.util.Objects;
+
+public class SemanticVersion {
+
+ public static SemanticVersion parse(String version) {
+ int majorEnd = version.indexOf('.');
+ if (majorEnd <= 0) {
+ throw new IllegalArgumentException("Invalid semantic version: " + version);
+ }
+ int minorEnd = version.indexOf('.', majorEnd + 1);
+ if (minorEnd <= majorEnd) {
+ throw new IllegalArgumentException("Invalid semantic version: " + version);
+ }
+ // No current support for extensions.
+ int patchEnd = version.length();
+ int major;
+ int minor;
+ int patch;
+ try {
+ major = Integer.parseInt(version.substring(0, majorEnd));
+ minor = Integer.parseInt(version.substring(majorEnd + 1, minorEnd));
+ patch = Integer.parseInt(version.substring(minorEnd + 1, patchEnd));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid semantic version: " + version, e);
+ }
+ return new SemanticVersion(major, minor, patch);
+ }
+
+ private final int major;
+ private final int minor;
+ private final int patch;
+
+ public SemanticVersion(int major, int minor, int patch) {
+ this.major = major;
+ this.minor = minor;
+ this.patch = patch;
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public int getMinor() {
+ return minor;
+ }
+
+ public int getPatch() {
+ return patch;
+ }
+
+ public boolean isNewerOrEqual(SemanticVersion other) {
+ if (major != other.major) {
+ return major > other.major;
+ }
+ if (minor != other.minor) {
+ return minor > other.minor;
+ }
+ return patch >= other.patch;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SemanticVersion)) {
+ return false;
+ }
+ SemanticVersion other = (SemanticVersion) obj;
+ return major == other.major && minor == other.minor && patch == other.patch;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(major, minor, patch);
+ }
+
+ @Override
+ public String toString() {
+ return "" + major + "." + minor + "." + patch;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
index 70518d7..a800801 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.utils.ExceptionDiagnostic;
+import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -71,15 +72,20 @@
}
public static Matcher<Diagnostic> diagnosticPosition(Position position) {
+ return diagnosticPosition(CoreMatchers.equalTo(position));
+ }
+
+ public static Matcher<Diagnostic> diagnosticPosition(Matcher<Position> positionMatcher) {
return new DiagnosticsMatcher() {
@Override
protected boolean eval(Diagnostic diagnostic) {
- return diagnostic.getPosition().equals(position);
+ return positionMatcher.matches(diagnostic.getPosition());
}
@Override
protected void explain(Description description) {
- description.appendText("position ").appendText(position.getDescription());
+ description.appendText("position ");
+ positionMatcher.describeTo(description);
}
};
}
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 8bc4820..18571d0 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.debug.CfDebugTestConfig;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ObjectArrays;
@@ -150,4 +151,9 @@
public JvmTestBuilder addVmArguments(String... arguments) {
return addVmArguments(Arrays.asList(arguments));
}
+
+ public JvmTestBuilder addAndroidBuildVersion() {
+ addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=10000");
+ return addProgramClasses(AndroidBuildVersion.class);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/PositionMatcher.java b/src/test/java/com/android/tools/r8/PositionMatcher.java
new file mode 100644
index 0000000..4fca1c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/PositionMatcher.java
@@ -0,0 +1,27 @@
+// 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;
+
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public abstract class PositionMatcher extends TypeSafeMatcher<Position> {
+
+ public static Matcher<Position> positionLine(int line) {
+ return new PositionMatcher() {
+ @Override
+ protected boolean matchesSafely(Position position) {
+ return position instanceof TextPosition && ((TextPosition) position).getLine() == line;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("with line " + line);
+ }
+ };
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 39356e1..0816496 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -243,6 +243,11 @@
return self();
}
+ public CR setSystemProperty(String name, String value) {
+ vmArguments.add("-D" + name + "=" + value);
+ return self();
+ }
+
public Path writeToZip() throws IOException {
Path file = state.getNewTempFolder().resolve("out.zip");
writeToZip(file);
@@ -429,6 +434,13 @@
withArt6Plus64BitsLib && vm.getVersion().isAtLeast(DexVm.Version.V6_0_1)
? builder -> builder.appendArtOption("--64")
: builder -> {};
+ commandConsumer =
+ commandConsumer.andThen(
+ builder -> {
+ for (String vmArgument : vmArguments) {
+ builder.appendArtOption(vmArgument);
+ }
+ });
ProcessResult result =
ToolHelper.runArtRaw(
classPath, mainClass, commandConsumer, vm, withArtFrameworks, arguments);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index ce03733..ae249ac 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -61,6 +62,14 @@
private PrintStream oldStderr = null;
protected OutputMode outputMode = OutputMode.DexIndexed;
+ private boolean isAndroidBuildVersionAdded = false;
+
+ public T addAndroidBuildVersion() {
+ addProgramClasses(AndroidBuildVersion.class);
+ isAndroidBuildVersionAdded = true;
+ return self();
+ }
+
TestCompilerBuilder(TestState state, B builder, Backend backend) {
super(state, builder);
this.backend = backend;
@@ -132,6 +141,9 @@
cr =
internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build))
.addRunClasspathFiles(additionalRunClassPath);
+ if (isAndroidBuildVersionAdded) {
+ cr.setSystemProperty(AndroidBuildVersion.PROPERTY, "" + builder.getMinApiLevel());
+ }
return cr;
} finally {
if (stdout != null) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BackwardsCompatibleSpecificationTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BackwardsCompatibleSpecificationTest.java
new file mode 100644
index 0000000..54f3acb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BackwardsCompatibleSpecificationTest.java
@@ -0,0 +1,55 @@
+// 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.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BackwardsCompatibleSpecificationTest extends TestBase {
+
+ private static final List<String> RELEASES = ImmutableList.of("2.0.74");
+
+ @Parameterized.Parameters(name = "{1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withNoneRuntime().build(), RELEASES);
+ }
+
+ private final Path desugaredLib = ToolHelper.getDesugarJDKLibs();
+ private final Path desugaredSpec = ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING;
+ private final String release;
+
+ public BackwardsCompatibleSpecificationTest(TestParameters parameters, String release) {
+ assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+ this.release = release;
+ }
+
+ private Path getReleaseJar() {
+ return Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8-releases", release, "r8lib.jar");
+ }
+
+ @Test
+ public void test() throws Exception {
+ ProcessResult result =
+ ToolHelper.runJava(
+ getReleaseJar(),
+ "com.android.tools.r8.L8",
+ "--desugared-lib",
+ desugaredSpec.toString(),
+ desugaredLib.toString());
+ assertEquals(result.toString(), 0, result.exitCode);
+ }
+}
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/DesugaredLibraryConfigurationParsingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
new file mode 100644
index 0000000..07c0703
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
@@ -0,0 +1,319 @@
+// 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 com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+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.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+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.AbortException;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SemanticVersion;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibraryConfigurationParsingTest extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public DesugaredLibraryConfigurationParsingTest(TestParameters parameters) {
+ assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+ }
+
+ final AndroidApiLevel minApi = AndroidApiLevel.B;
+ final boolean libraryCompilation = true;
+
+ final DexItemFactory factory = new DexItemFactory();
+ final Origin origin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "Test Origin";
+ }
+ };
+
+ final Map<String, Object> TEMPLATE =
+ ImmutableMap.<String, Object>builder()
+ .put(
+ "configuration_format_version",
+ DesugaredLibraryConfigurationParser.MAX_SUPPORTED_VERSION)
+ .put("group_id", "com.tools.android")
+ .put("artifact_id", "desugar_jdk_libs")
+ .put("version", DesugaredLibraryConfigurationParser.MIN_SUPPORTED_VERSION.toString())
+ .put("required_compilation_api_level", 1)
+ .put("synthesized_library_classes_package_prefix", "j$.")
+ .put("common_flags", Collections.emptyList())
+ .put("program_flags", Collections.emptyList())
+ .put("library_flags", Collections.emptyList())
+ .build();
+
+ private LinkedHashMap<String, Object> template() {
+ return new LinkedHashMap<>(TEMPLATE);
+ }
+
+ private DesugaredLibraryConfigurationParser parser(DiagnosticsHandler handler) {
+ return new DesugaredLibraryConfigurationParser(
+ factory, new Reporter(handler), libraryCompilation, minApi.getLevel());
+ }
+
+ private DesugaredLibraryConfiguration runPassing(String resource) {
+ return runPassing(StringResource.fromString(resource, origin));
+ }
+
+ private DesugaredLibraryConfiguration runPassing(StringResource resource) {
+ TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+ DesugaredLibraryConfiguration config = parser(handler).parse(resource);
+ handler.assertNoMessages();
+ return config;
+ }
+
+ private void runFailing(String json, Consumer<TestDiagnosticMessages> checker) {
+ TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+ try {
+ parser(handler).parse(StringResource.fromString(json, origin));
+ fail("Expected failure");
+ } catch (AbortException e) {
+ checker.accept(handler);
+ }
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ // Just test that the reference file parses without issues.
+ DesugaredLibraryConfiguration config =
+ runPassing(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+ assertEquals(libraryCompilation, config.isLibraryCompilation());
+ }
+
+ @Test
+ public void testEmpty() {
+ runFailing(
+ "",
+ diagnostics -> {
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(containsString("Not a JSON Object")),
+ diagnosticOrigin(origin)));
+ });
+ }
+
+ @Test
+ public void testRequiredKeys() {
+ ImmutableList<String> requiredKeys =
+ ImmutableList.of(
+ "configuration_format_version",
+ "group_id",
+ "artifact_id",
+ "version",
+ "required_compilation_api_level",
+ "synthesized_library_classes_package_prefix",
+ "common_flags",
+ "program_flags",
+ "library_flags");
+ for (String key : requiredKeys) {
+ Map<String, Object> data = template();
+ data.remove(key);
+ runFailing(
+ toJson(data),
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(containsString("Invalid desugared library configuration")),
+ diagnosticMessage(containsString("Expected required key '" + key + "'")),
+ diagnosticOrigin(origin))));
+ }
+ }
+
+ @Test
+ public void testUnsupportedVersion() {
+ LinkedHashMap<String, Object> data = template();
+ SemanticVersion minVersion = DesugaredLibraryConfigurationParser.MIN_SUPPORTED_VERSION;
+ data.put(
+ "version",
+ new SemanticVersion(minVersion.getMajor(), minVersion.getMinor(), minVersion.getPatch() - 1)
+ .toString());
+ runFailing(
+ toJson(data),
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(containsString("upgrade the desugared library")),
+ diagnosticOrigin(origin))));
+ }
+
+ @Test
+ public void testUnsupportedAbove() {
+ LinkedHashMap<String, Object> data = template();
+ data.put("configuration_format_version", 100000);
+ runFailing(
+ toJson(data),
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(containsString("upgrade the D8/R8 compiler")),
+ diagnosticOrigin(origin))));
+ }
+
+ @Test
+ public void testCustomAndWrapperOverlap() {
+ LinkedHashMap<String, Object> data = template();
+ data.put(
+ "common_flags",
+ ImmutableList.of(
+ ImmutableMap.of(
+ "api_level_below_or_equal",
+ 100000,
+ "custom_conversion",
+ ImmutableMap.of("java.util.Foo", "j$.util.FooConv"),
+ "wrapper_conversion",
+ ImmutableList.of("java.util.Foo"))));
+ runFailing(
+ toJson(data),
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(containsString("Duplicate types")),
+ diagnosticMessage(containsString("java.util.Foo")),
+ diagnosticOrigin(origin))));
+ }
+
+ @Test
+ public void testRedefinition() {
+ LinkedHashMap<String, Object> data = template();
+ data.put(
+ "common_flags",
+ ImmutableList.of(
+ ImmutableMap.of(
+ "api_level_below_or_equal",
+ 100000,
+ "custom_conversion",
+ ImmutableMap.of("java.util.Foo", "j$.util.FooConv1"))));
+ data.put(
+ "library_flags",
+ ImmutableList.of(
+ ImmutableMap.of(
+ "api_level_below_or_equal",
+ 100000,
+ "custom_conversion",
+ ImmutableMap.of("java.util.Foo", "j$.util.FooConv2"))));
+ runFailing(
+ toJson(data),
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticMessage(containsString("Duplicate assignment of key")),
+ diagnosticMessage(containsString("java.util.Foo")),
+ diagnosticMessage(containsString("custom_conversion")),
+ diagnosticOrigin(origin))));
+ }
+
+ @Test
+ public void testDuplicate() {
+ LinkedHashMap<String, Object> data = template();
+ data.put(
+ "common_flags",
+ ImmutableList.of(
+ ImmutableMap.of(
+ "api_level_below_or_equal",
+ 100000,
+ "custom_conversion",
+ ImmutableMap.of(
+ "java.util.Foo", "j$.util.FooConv1",
+ "java.util.Foo2", "j$.util.FooConv2"))));
+ // The gson parser will overwrite the key in order during parsing, thus hiding potential issues.
+ DesugaredLibraryConfiguration config = runPassing(toJson(data).replace("Foo2", "Foo"));
+ assertEquals(
+ Collections.singletonList("java.util.Foo"),
+ config.getCustomConversions().keySet().stream()
+ .map(DexType::toString)
+ .collect(Collectors.toList()));
+ assertEquals(
+ Collections.singletonList("j$.util.FooConv2"),
+ config.getCustomConversions().values().stream()
+ .map(DexType::toString)
+ .collect(Collectors.toList()));
+ }
+
+ // JSON building helpers.
+ // This does not use gson to make the text input construction independent of gson.
+
+ private static String toJson(Map<String, Object> data) {
+ StringBuilder builder = new StringBuilder();
+ toJsonObject(data, builder);
+ return builder.toString();
+ }
+
+ private static void toJsonObject(Map<String, Object> data, StringBuilder builder) {
+ StringUtils.append(
+ builder,
+ ListUtils.map(
+ data.entrySet(),
+ entry -> "\n " + quote(entry.getKey()) + ": " + toJsonElement(entry.getValue())),
+ ", ",
+ BraceType.TUBORG);
+ }
+
+ private static String toJsonElement(Object element) {
+ StringBuilder builder = new StringBuilder();
+ toJsonElement(element, builder);
+ return builder.toString();
+ }
+
+ private static void toJsonElement(Object element, StringBuilder builder) {
+ if (element instanceof String) {
+ builder.append(quote((String) element));
+ } else if (element instanceof Integer) {
+ builder.append(element);
+ } else if (element instanceof List) {
+ toJsonList((List<Object>) element, builder);
+ } else if (element instanceof Map) {
+ toJsonObject((Map<String, Object>) element, builder);
+ } else {
+ throw new IllegalStateException("Unexpected object type: " + element.getClass());
+ }
+ }
+
+ private static void toJsonList(List<Object> element, StringBuilder builder) {
+ StringUtils.append(
+ builder, ListUtils.map(element, o -> "\n " + toJsonElement(o)), ", ", BraceType.SQUARE);
+ }
+
+ private static String quote(String str) {
+ return "\"" + str + "\"";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
new file mode 100644
index 0000000..846b65d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -0,0 +1,304 @@
+// 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 com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.GenerateLintFiles;
+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.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ExtractWrapperTypesTest extends TestBase {
+
+ // Filter on types that do not need to be considered for wrapping.
+ private static boolean doesNotNeedWrapper(String type, Set<String> customConversions) {
+ return excludePackage(type)
+ || NOT_NEEDED_NOT_IN_DOCS.contains(type)
+ || FINAL_CLASSES.contains(type)
+ || customConversions.contains(type);
+ }
+
+ private static boolean excludePackage(String type) {
+ return type.startsWith("java.lang.")
+ || type.startsWith("java.nio.")
+ || type.startsWith("java.security.")
+ || type.startsWith("java.net.")
+ || type.startsWith("java.awt.")
+ || type.startsWith("java.util.concurrent.");
+ }
+
+ // Types not picked up by the android.jar scan but for which wrappers are needed.
+ private static final Set<String> ADDITIONAL_WRAPPERS = ImmutableSet.of();
+
+ // Types not in API docs, referenced in android.jar and must be wrapped.
+ private static final Set<String> NEEDED_BUT_NOT_IN_DOCS = ImmutableSet.of();
+
+ // Types not in API docs, referenced in android.jar but need not be wrapped.
+ private static final Set<String> NOT_NEEDED_NOT_IN_DOCS =
+ ImmutableSet.of(
+ "java.util.Base64$Decoder",
+ "java.util.Base64$Encoder",
+ "java.util.Calendar$Builder",
+ "java.util.Locale$Builder",
+ "java.util.Locale$Category",
+ "java.util.Locale$FilteringMode",
+ "java.util.SplittableRandom");
+
+ // List of referenced final classes (cannot be wrapper converted) with no custom conversions.
+ private static final Set<String> FINAL_CLASSES =
+ ImmutableSet.of(
+ // TODO(b/159304624): Does this need custom conversion?
+ "java.time.Period");
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ // TODO: parameterize to check both api<=23 as well as 23<api<26 for which the spec differs.
+ private final AndroidApiLevel minApi = AndroidApiLevel.B;
+ private final AndroidApiLevel targetApi = AndroidApiLevel.Q;
+
+ public ExtractWrapperTypesTest(TestParameters parameters) {
+ assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+ }
+
+ @Test
+ public void checkConsistency() {
+ List<Set<String>> sets =
+ ImmutableList.of(
+ ADDITIONAL_WRAPPERS, NEEDED_BUT_NOT_IN_DOCS, NOT_NEEDED_NOT_IN_DOCS, FINAL_CLASSES);
+ for (Set<String> set1 : sets) {
+ for (Set<String> set2 : sets) {
+ if (set1 != set2) {
+ assertEquals(Collections.emptySet(), Sets.intersection(set1, set2));
+ }
+ }
+ }
+ for (Set<String> set : sets) {
+ for (String type : set) {
+ assertFalse(excludePackage(type));
+ }
+ }
+ }
+
+ @Test
+ public void test() throws Exception {
+ CodeInspector desugaredApiJar = getDesugaredApiJar();
+ Set<ClassReference> preDesugarTypes = getPreDesugarTypes();
+
+ DesugaredLibraryConfiguration conf = getDesugaredLibraryConfiguration();
+ Set<String> wrappersInSpec =
+ conf.getWrapperConversions().stream().map(DexType::toString).collect(Collectors.toSet());
+ Set<String> customConversionsInSpec =
+ conf.getCustomConversions().keySet().stream()
+ .map(DexType::toString)
+ .collect(Collectors.toSet());
+ assertEquals(
+ Collections.emptySet(), Sets.intersection(wrappersInSpec, customConversionsInSpec));
+ assertEquals(Collections.emptySet(), Sets.intersection(FINAL_CLASSES, customConversionsInSpec));
+
+ CodeInspector nonDesugaredJar = new CodeInspector(ToolHelper.getAndroidJar(targetApi));
+ Map<ClassReference, Set<MethodReference>> directWrappers =
+ getDirectlyReferencedWrapperTypes(
+ desugaredApiJar, preDesugarTypes, nonDesugaredJar, customConversionsInSpec);
+ Map<ClassReference, Set<ClassReference>> indirectWrappers =
+ getIndirectlyReferencedWrapperTypes(
+ directWrappers, preDesugarTypes, nonDesugaredJar, customConversionsInSpec);
+
+ {
+ Set<String> missingWrappers = getMissingWrappers(directWrappers, wrappersInSpec);
+ assertTrue(
+ "Missing direct wrappers:\n" + String.join("\n", missingWrappers),
+ missingWrappers.isEmpty());
+ }
+
+ {
+ Set<String> missingWrappers = getMissingWrappers(indirectWrappers, wrappersInSpec);
+ assertTrue(
+ "Missing indirect wrappers:\n" + String.join("\n", missingWrappers),
+ missingWrappers.isEmpty());
+ }
+
+ Set<String> additionalWrappers = new TreeSet<>();
+ for (String wrapper : wrappersInSpec) {
+ ClassReference item = Reference.classFromTypeName(wrapper);
+ if (!directWrappers.containsKey(item)
+ && !indirectWrappers.containsKey(item)
+ && !ADDITIONAL_WRAPPERS.contains(wrapper)) {
+ additionalWrappers.add(wrapper);
+ }
+ }
+ assertTrue(
+ "Additional wrapper:\n" + String.join("\n", additionalWrappers),
+ additionalWrappers.isEmpty());
+
+ assertEquals(
+ directWrappers.size() + indirectWrappers.size() + ADDITIONAL_WRAPPERS.size(),
+ wrappersInSpec.size());
+ }
+
+ private static <T> Set<String> getMissingWrappers(
+ Map<ClassReference, Set<T>> expected, Set<String> wrappersInSpec) {
+ Set<String> missingWrappers = new TreeSet<>();
+ for (ClassReference addition : expected.keySet()) {
+ String item = descriptorToJavaType(addition.getDescriptor());
+ if (!wrappersInSpec.contains(item)) {
+ missingWrappers.add(item + " referenced from: " + expected.get(addition));
+ }
+ }
+ return missingWrappers;
+ }
+
+ private DesugaredLibraryConfiguration getDesugaredLibraryConfiguration() {
+ DesugaredLibraryConfigurationParser parser =
+ new DesugaredLibraryConfigurationParser(
+ new DexItemFactory(), null, true, minApi.getLevel());
+ return parser.parse(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+ }
+
+ private Map<ClassReference, Set<MethodReference>> getDirectlyReferencedWrapperTypes(
+ CodeInspector desugaredApiJar,
+ Set<ClassReference> preDesugarTypes,
+ CodeInspector nonDesugaredJar,
+ Set<String> customConversions) {
+ Map<ClassReference, Set<MethodReference>> directWrappers = new HashMap<>();
+ nonDesugaredJar.forAllClasses(
+ clazz -> {
+ clazz.forAllMethods(
+ method -> {
+ if (!method.isPublic() && !method.isProtected()) {
+ return;
+ }
+ if (desugaredApiJar.method(method.asMethodReference()).isPresent()) {
+ return;
+ }
+ Consumer<ClassReference> adder =
+ t ->
+ directWrappers
+ .computeIfAbsent(t, k -> new HashSet<>())
+ .add(method.asMethodReference());
+ MethodSignature signature = method.getFinalSignature().asMethodSignature();
+ addType(adder, signature.type, preDesugarTypes, customConversions);
+ for (String parameter : signature.parameters) {
+ addType(adder, parameter, preDesugarTypes, customConversions);
+ }
+ });
+ });
+ return directWrappers;
+ }
+
+ private Map<ClassReference, Set<ClassReference>> getIndirectlyReferencedWrapperTypes(
+ Map<ClassReference, Set<MethodReference>> directWrappers,
+ Set<ClassReference> existing,
+ CodeInspector latest,
+ Set<String> customConversions) {
+ Map<ClassReference, Set<ClassReference>> indirectWrappers = new HashMap<>();
+ WorkList<ClassReference> worklist = WorkList.newEqualityWorkList(directWrappers.keySet());
+ while (worklist.hasNext()) {
+ ClassReference reference = worklist.next();
+ ClassSubject clazz = latest.clazz(reference);
+ clazz.forAllVirtualMethods(
+ method -> {
+ assertTrue(method.toString(), method.isPublic() || method.isProtected());
+ MethodSignature signature = method.getFinalSignature().asMethodSignature();
+ Consumer<ClassReference> adder =
+ t -> {
+ if (worklist.addIfNotSeen(t)) {
+ indirectWrappers.computeIfAbsent(t, k -> new HashSet<>()).add(reference);
+ }
+ };
+ addType(adder, signature.type, existing, customConversions);
+ for (String parameter : signature.parameters) {
+ addType(adder, parameter, existing, customConversions);
+ }
+ });
+ }
+ return indirectWrappers;
+ }
+
+ private Set<ClassReference> getPreDesugarTypes() throws IOException {
+ Set<ClassReference> existing = new HashSet<>();
+ Path androidJar = ToolHelper.getAndroidJar(minApi);
+ new CodeInspector(androidJar)
+ .forAllClasses(
+ clazz -> {
+ if (clazz.getFinalName().startsWith("java.")) {
+ existing.add(Reference.classFromTypeName(clazz.getFinalName()));
+ }
+ });
+ return existing;
+ }
+
+ private CodeInspector getDesugaredApiJar() throws Exception {
+ Path out = temp.newFolder().toPath();
+ GenerateLintFiles desugaredApi =
+ new GenerateLintFiles(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(), out.toString());
+ desugaredApi.run(targetApi.getLevel());
+ return new CodeInspector(
+ out.resolve("compile_api_level_" + targetApi.getLevel())
+ .resolve("desugared_apis_" + targetApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
+ }
+
+ private void addType(
+ Consumer<ClassReference> additions,
+ String type,
+ Set<ClassReference> preDesugarTypes,
+ Set<String> customConversions) {
+ if (type.equals("void")) {
+ return;
+ }
+ TypeReference typeReference = Reference.typeFromTypeName(type);
+ if (typeReference.isArray()) {
+ typeReference = typeReference.asArray().getBaseType();
+ }
+ if (typeReference.isClass()) {
+ ClassReference clazz = typeReference.asClass();
+ String clazzType = descriptorToJavaType(clazz.getDescriptor());
+ if (clazzType.startsWith("java.")
+ && !doesNotNeedWrapper(clazzType, customConversions)
+ && !preDesugarTypes.contains(clazz)) {
+ additions.accept(clazz);
+ }
+ }
+ }
+}
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..e8aa581 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,7 @@
import java.util.function.IntSupplier;
import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
-import org.junit.Assert;
-import org.junit.Assume;
+import java.util.function.Supplier;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,6 +68,7 @@
.addLibraryClasses(CustomLibClass.class)
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
+ .assertNoMessages()
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
@@ -101,43 +100,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) {
@@ -146,16 +108,16 @@
BiFunction<String, String, Character> biFunction =
CustomLibClass.mixBiFunctions((String i, String j) -> i + j, (String s) -> s.charAt(1));
System.out.println(biFunction.apply("1", "2"));
- BooleanSupplier booleanSupplier = CustomLibClass.mixBoolSuppliers(() -> true, () -> false);
+ BooleanSupplier booleanSupplier =
+ () -> CustomLibClass.mixBoolSuppliers(() -> true, () -> false).get();
System.out.println(booleanSupplier.getAsBoolean());
LongConsumer longConsumer = CustomLibClass.mixLong(() -> 1L, System.out::println);
longConsumer.accept(2L);
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());
+ System.out.println(CustomLibClass.getDoubleSupplier().get());
}
static class Object1 {}
@@ -203,13 +165,18 @@
return operator.andThen(function);
}
- public static BooleanSupplier mixBoolSuppliers(
- BooleanSupplier supplier1, BooleanSupplier supplier2) {
- return () -> supplier1.getAsBoolean() && supplier2.getAsBoolean();
+ // BooleanSupplier is not a wrapped type, so it can't be placed on the boundary.
+ public static Supplier<Boolean> mixBoolSuppliers(
+ Supplier<Boolean> supplier1, Supplier<Boolean> supplier2) {
+ BooleanSupplier wrap1 = supplier1::get;
+ BooleanSupplier wrap2 = supplier2::get;
+ return () -> wrap1.getAsBoolean() && wrap2.getAsBoolean();
}
- public static LongConsumer mixLong(LongSupplier supplier, LongConsumer consumer) {
- return l -> consumer.accept(l + supplier.getAsLong());
+ // LongSupplier is not a wrapped type, so it can't be placed on the boundary.
+ public static LongConsumer mixLong(Supplier<Long> supplier, LongConsumer consumer) {
+ LongSupplier wrap = supplier::get;
+ return l -> consumer.accept(l + wrap.getAsLong());
}
public static DoublePredicate mixPredicate(
@@ -217,12 +184,16 @@
return predicate1.and(predicate2).and(predicate3);
}
- public static int extractInt(IntSupplier supplier) {
- return supplier.getAsInt();
+ // IntSupplier is not a wrapped type, so it can't be placed on the boundary.
+ public static int extractInt(Supplier<Integer> supplier) {
+ IntSupplier wrap = supplier::get;
+ return wrap.getAsInt();
}
- public static DoubleSupplier getDoubleSupplier() {
- return () -> 42.0;
+ // DoubleSupplier is not a wrapped type, so it can't be placed on the boundary.
+ public static Supplier<Double> getDoubleSupplier() {
+ DoubleSupplier supplier = () -> 42.0;
+ return supplier::getAsDouble;
}
}
}
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
deleted file mode 100644
index 4c1caf7..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
-
-import static org.junit.Assert.assertEquals;
-
-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.ir.desugar.DesugaredLibraryWrapperSynthesizer;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
-import java.util.Arrays;
-import org.junit.Test;
-
-public class WrapperMergeTest extends DesugaredLibraryTestBase {
-
- @Test
- public void testWrapperMerge() throws Exception {
- // Multiple wrapper classes have to be merged here.
- Path path1 = testForD8()
- .addProgramClasses(Executor1.class)
- .setMinApi(AndroidApiLevel.B)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
- .inspect(this::assertWrappers)
- .writeToZip();
- Path path2 = testForD8()
- .addProgramClasses(Executor2.class)
- .setMinApi(AndroidApiLevel.B)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
- .inspect(this::assertWrappers)
- .writeToZip();
- testForD8()
- .addProgramFiles(path1, path2)
- .compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class)
- .assertSuccessWithOutput(StringUtils.lines("[1, 2, 3]"));
- }
-
- private void assertWrappers(CodeInspector inspector) {
- assertEquals(2,inspector.allClasses().stream().filter(c -> c.getOriginalName().contains(
- DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)).count());
- }
-
- static class Executor1 {
-
- public static void main(String[] args) {
- int[] ints = new int[3];
- Arrays.setAll(ints,x->x+1);
- System.out.println(Arrays.toString(ints));
- }
- }
-
- static class Executor2 {
-
- public static void main(String[] args) {
- int[] ints = new int[3];
- Arrays.setAll(ints,x->x+2);
- System.out.println(Arrays.toString(ints));
- }
- }
-
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
new file mode 100644
index 0000000..cbab10c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperPlacementTest.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+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.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.stream.Stream;
+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 WrapperPlacementTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED = StringUtils.lines("[1, 2, 3]", "[2, 3, 4]");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public WrapperPlacementTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addAndroidBuildVersion()
+ .addProgramClassesAndInnerClasses(MyArrays1.class)
+ .addProgramClassesAndInnerClasses(MyArrays2.class)
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testNoWrappers() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ // No wrappers are made during program compilation.
+ Path path1 = compileWithCoreLibraryDesugaring(MyArrays1.class);
+ Path path2 = compileWithCoreLibraryDesugaring(MyArrays2.class);
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .addAndroidBuildVersion()
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .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);
+ }
+
+ private Path compileWithCoreLibraryDesugaring(Class<?> clazz) throws Exception {
+ return testForD8()
+ .addProgramClassesAndInnerClasses(clazz)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertNoWrappers)
+ .writeToZip();
+ }
+
+ private boolean hasNativeIntUnaryOperator() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ private void assertCoreLibContainsWrappers(Path coreLib) throws IOException {
+ CodeInspector inspector = new CodeInspector(coreLib);
+ Stream<FoundClassSubject> wrappers = getWrappers(inspector);
+ assertNotEquals(0, wrappers.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 {
+
+ interface IntGenerator {
+ int generate(int index);
+ }
+
+ public static void setAll(int[] ints, IntGenerator generator) {
+ if (AndroidBuildVersion.VERSION >= 24) {
+ java.util.Arrays.setAll(ints, generator::generate);
+ } else {
+ for (int i = 0; i < ints.length; i++) {
+ ints[i] = generator.generate(i);
+ }
+ }
+ }
+ }
+
+ static class MyArrays2 {
+
+ interface IntGenerator {
+ int generate(int index);
+ }
+
+ public static void setAll(int[] ints, IntGenerator generator) {
+ if (AndroidBuildVersion.VERSION >= 24) {
+ java.util.Arrays.setAll(ints, generator::generate);
+ } else {
+ for (int i = 0; i < ints.length; i++) {
+ ints[i] = generator.generate(i);
+ }
+ }
+ }
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ int[] ints = new int[3];
+ MyArrays1.setAll(ints, x -> x + 1);
+ System.out.println(Arrays.toString(ints));
+ MyArrays2.setAll(ints, x -> x + 2);
+ System.out.println(Arrays.toString(ints));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
new file mode 100644
index 0000000..ab3ea7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
@@ -0,0 +1,14 @@
+// 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.testing;
+
+/**
+ * Stub class to simulate having a Build.VERSION property in headless tests.
+ *
+ * <p>Use test builder addAndroidBuildVersion() methods when used in tests.
+ */
+public class AndroidBuildVersion {
+ public static final String PROPERTY = "com.android.tools.r8.testing.AndroidBuildVersion.VERSION";
+ public static int VERSION = Integer.parseInt(System.getProperty(PROPERTY));
+}
diff --git a/src/test/java/com/android/tools/r8/utils/SemanticVersionTests.java b/src/test/java/com/android/tools/r8/utils/SemanticVersionTests.java
new file mode 100644
index 0000000..8f0dd61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/SemanticVersionTests.java
@@ -0,0 +1,48 @@
+// 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.utils;
+
+import static com.android.tools.r8.utils.SemanticVersion.parse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SemanticVersionTests extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public SemanticVersionTests(TestParameters parameters) {
+ assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+ }
+
+ @Test
+ public void test() throws Exception {
+ assertTrue(parse("1.0.1").isNewerOrEqual(parse("1.0.0")));
+ assertFalse(parse("1.0.1").isNewerOrEqual(parse("1.1.0")));
+ assertTrue(parse("1.1.0").isNewerOrEqual(parse("1.0.1")));
+ assertFalse(parse("1.1.1").isNewerOrEqual(parse("2.0.0")));
+
+ assertTrue(parse("2.0.0").isNewerOrEqual(parse("1.1.1")));
+ assertTrue(parse("42.42.42").isNewerOrEqual(parse("9.9.9")));
+ assertTrue(parse("9.42.42").isNewerOrEqual(parse("9.9.9")));
+ assertTrue(parse("9.9.42").isNewerOrEqual(parse("9.9.9")));
+
+ assertFalse(parse("1.1.1").isNewerOrEqual(parse("2.0.0")));
+ assertFalse(parse("9.9.9").isNewerOrEqual(parse("42.42.42")));
+ assertFalse(parse("9.9.9").isNewerOrEqual(parse("9.42.42")));
+ assertFalse(parse("9.9.9").isNewerOrEqual(parse("9.9.42")));
+ }
+}
diff --git a/third_party/r8-releases/2.0.74.tar.gz.sha1 b/third_party/r8-releases/2.0.74.tar.gz.sha1
new file mode 100644
index 0000000..7359e6d
--- /dev/null
+++ b/third_party/r8-releases/2.0.74.tar.gz.sha1
@@ -0,0 +1 @@
+6903a4fb98ef23da4d40e1b08998a390578dd0ef
\ No newline at end of file