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