Make Machine specification backward-compatible.
Bug: b/330833507
Change-Id: Iffe839f6d6989f56074d01189ce2fd4a863bd0d6
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 0020bab..f2dfda6 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -63,14 +63,6 @@
"rewrite_prefix": {
"java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
},
- "emulate_interface": {
- "java.util.Collection": {
- "rewrittenType": "j$.util.Collection",
- "emulatedMethods": [
- "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
- ]
- }
- },
"retarget_method": {
"java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
"java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -99,6 +91,14 @@
"rewrite_prefix": {
"java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
},
+ "emulate_interface": {
+ "java.util.Collection": {
+ "rewrittenType": "j$.util.Collection",
+ "emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+ ]
+ }
+ },
"retarget_method": {
"java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
"java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -252,6 +252,7 @@
"java.util.Collection": {
"rewrittenType": "j$.util.Collection",
"emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
"java.util.stream.Stream java.util.Collection#stream()",
"java.util.stream.Stream java.util.Collection#parallelStream()",
"java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index aa8b1c0..1aff889 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -77,14 +77,6 @@
"java.io.DesugarInputStream": "j$.io.DesugarInputStream",
"java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
},
- "emulate_interface": {
- "java.util.Collection": {
- "rewrittenType": "j$.util.Collection",
- "emulatedMethods": [
- "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
- ]
- }
- },
"retarget_method": {
"java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
"java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -119,6 +111,14 @@
"rewrite_prefix": {
"java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
},
+ "emulate_interface": {
+ "java.util.Collection": {
+ "rewrittenType": "j$.util.Collection",
+ "emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+ ]
+ }
+ },
"retarget_method": {
"java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
"java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -402,6 +402,7 @@
"java.util.Collection": {
"rewrittenType": "j$.util.Collection",
"emulatedMethods": [
+ "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
"java.util.stream.Stream java.util.Collection#stream()",
"java.util.stream.Stream java.util.Collection#parallelStream()",
"java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
index e13fb7c..9f9738e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
@@ -24,6 +24,8 @@
public ApiLevelRange(
AndroidApiLevel apiLevelBelowOrEqual, AndroidApiLevel apiLevelGreaterOrEqual) {
+ assert apiLevelGreaterOrEqual == null
+ || apiLevelBelowOrEqual.isGreaterThanOrEqualTo(apiLevelGreaterOrEqual);
this.apiLevelBelowOrEqual = apiLevelBelowOrEqual;
this.apiLevelGreaterOrEqual = apiLevelGreaterOrEqual;
}
@@ -40,6 +42,15 @@
return apiLevelGreaterOrEqual != null;
}
+ public boolean overlap(ApiLevelRange other) {
+ AndroidApiLevel start =
+ apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : apiLevelGreaterOrEqual;
+ AndroidApiLevel otherStart =
+ other.apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : other.apiLevelGreaterOrEqual;
+ return start.isLessThanOrEqualTo(other.apiLevelBelowOrEqual)
+ && otherStart.isLessThanOrEqualTo(apiLevelBelowOrEqual);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -74,4 +85,13 @@
}
return apiLevelGreaterOrEqual.compareTo(other.apiLevelGreaterOrEqual);
}
+
+ @Override
+ public String toString() {
+ return "[ "
+ + (apiLevelGreaterOrEqual == null ? "B" : apiLevelGreaterOrEqual)
+ + " ; "
+ + apiLevelBelowOrEqual
+ + " ]";
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
index e3a9dff..80ca5b3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
-import com.google.common.collect.ImmutableMap;
import java.util.Map;
import java.util.Objects;
@@ -35,18 +34,6 @@
return emulatedMethods;
}
- public EmulatedInterfaceDescriptor merge(EmulatedInterfaceDescriptor other) {
- if (!rewrittenType.isIdenticalTo(other.getRewrittenType())) {
- throw new UnsupportedOperationException(
- "Emulated interface descriptor can only be merged on the same rewritten type.");
- }
- ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor> builder =
- ImmutableMap.builder();
- builder.putAll(getEmulatedMethods());
- builder.putAll(other.getEmulatedMethods());
- return new EmulatedInterfaceDescriptor(rewrittenType, builder.build());
- }
-
@Override
public Object[] toJsonStruct(
MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index f28dae7..d27ba9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -362,8 +362,8 @@
emulatedVirtualRetargetThroughEmulatedInterface = ImmutableMap.builder();
private final ImmutableMap.Builder<DexMethod, DexMethod[]> apiGenericTypesConversion =
ImmutableMap.builder();
- private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
- new IdentityHashMap<>();
+ private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
+ ImmutableMap.builder();
private final LinkedHashMap<DexType, WrapperDescriptor> wrappers = new LinkedHashMap<>();
private final ImmutableMap.Builder<DexType, DexType> legacyBackport = ImmutableMap.builder();
private final ImmutableSet.Builder<DexType> dontRetarget = ImmutableSet.builder();
@@ -409,12 +409,8 @@
nonEmulatedVirtualRetarget.put(src, dest);
}
- public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor newDescriptor) {
- assert newDescriptor != null;
- EmulatedInterfaceDescriptor oldDescriptor = emulatedInterfaces.get(src);
- EmulatedInterfaceDescriptor mergedDescriptor =
- oldDescriptor == null ? newDescriptor : newDescriptor.merge(oldDescriptor);
- emulatedInterfaces.put(src, mergedDescriptor);
+ public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor descriptor) {
+ emulatedInterfaces.put(src, descriptor);
}
public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
@@ -491,7 +487,7 @@
emulatedVirtualRetarget.build(),
emulatedVirtualRetargetThroughEmulatedInterface.build(),
apiGenericTypesConversion.build(),
- ImmutableMap.copyOf(emulatedInterfaces),
+ emulatedInterfaces.build(),
wrappers,
legacyBackport.build(),
dontRetarget.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
index 46ec6ba..f15da9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
@@ -56,7 +56,7 @@
public class MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter {
- static final int MACHINE_VERSION_NUMBER = 201;
+ static final int MACHINE_VERSION_NUMBER = 200;
private final DexItemFactory factory;
private final Map<String, String> packageMap = new TreeMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index c5c7348..843a915 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexApplication;
@@ -30,8 +31,10 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
public class HumanToMachineSpecificationConverter {
@@ -63,6 +66,8 @@
Map<ApiLevelRange, MachineRewritingFlags> libraryFlags =
convertRewritingFlagMap(humanSpec.getLibraryFlags(), synthesizedPrefix, true, identifier);
+ checkDisjointEmulatedInterfaceFlags(commonFlags, programFlags, libraryFlags);
+
MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
new MultiAPILevelMachineDesugaredLibrarySpecification(
humanSpec.getOrigin(), machineTopLevelFlags, commonFlags, libraryFlags, programFlags);
@@ -70,6 +75,56 @@
return machineSpec;
}
+ // For backward compatibility, there can be only one emulated interface flag for a given type at
+ // a given API level.
+ private void checkDisjointEmulatedInterfaceFlags(
+ Map<ApiLevelRange, MachineRewritingFlags> commonFlags,
+ Map<ApiLevelRange, MachineRewritingFlags> programFlags,
+ Map<ApiLevelRange, MachineRewritingFlags> libraryFlags) {
+ Set<DexType> commonTypes = emulatedInterfaceTypes(commonFlags);
+ Set<DexType> programTypes = emulatedInterfaceTypes(programFlags);
+ Set<DexType> libraryTypes = emulatedInterfaceTypes(libraryFlags);
+ if (!Sets.intersection(commonTypes, programTypes).isEmpty()
+ || !Sets.intersection(commonTypes, libraryTypes).isEmpty()
+ || !Sets.intersection(libraryTypes, programTypes).isEmpty()) {
+ throw new CompilationError("Cannot have emulated interface split across flag types");
+ }
+ checkEmulatedInterfaceMap(commonFlags);
+ checkEmulatedInterfaceMap(programFlags);
+ checkEmulatedInterfaceMap(libraryFlags);
+ }
+
+ private void checkEmulatedInterfaceMap(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+ Map<DexType, List<ApiLevelRange>> rangesForType = new IdentityHashMap<>();
+ flagMap.forEach(
+ (range, flags) ->
+ flags
+ .getEmulatedInterfaces()
+ .forEach(
+ (ei, descr) -> {
+ rangesForType.putIfAbsent(ei, new ArrayList<>());
+ rangesForType.get(ei).add(range);
+ }));
+ rangesForType.keySet().removeIf(t -> rangesForType.get(t).size() == 1);
+ rangesForType.forEach(
+ (type, ranges) -> {
+ for (ApiLevelRange range1 : ranges) {
+ for (ApiLevelRange range2 : ranges) {
+ if (!Objects.equals(range1, range2) && range1.overlap(range2)) {
+ throw new CompilationError(
+ "Unsupported Machine specification for " + type + " " + range1 + " " + range2);
+ }
+ }
+ }
+ });
+ }
+
+ private Set<DexType> emulatedInterfaceTypes(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+ Set<DexType> types = Sets.newIdentityHashSet();
+ flagMap.forEach((range, flags) -> types.addAll(flags.getEmulatedInterfaces().keySet()));
+ return types;
+ }
+
private Map<ApiLevelRange, MachineRewritingFlags> convertRewritingFlagMap(
Map<ApiLevelRange, HumanRewritingFlags> libFlags,
String synthesizedPrefix,
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
new file mode 100644
index 0000000..0747d96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, 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.backports;
+
+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.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApiLevelRangeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ApiLevelRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRange() {
+ ApiLevelRange rs = new ApiLevelRange(AndroidApiLevel.S, AndroidApiLevel.R);
+ ApiLevelRange op = new ApiLevelRange(AndroidApiLevel.P, AndroidApiLevel.O);
+ ApiLevelRange ir = new ApiLevelRange(AndroidApiLevel.R, AndroidApiLevel.I);
+ ApiLevelRange r = new ApiLevelRange(AndroidApiLevel.R, null);
+ ApiLevelRange i = new ApiLevelRange(AndroidApiLevel.I, null);
+
+ // Overlap with null.
+ assertTrue(i.overlap(r));
+ assertTrue(r.overlap(i));
+
+ // No overlap with null.
+ assertFalse(i.overlap(rs));
+ assertFalse(rs.overlap(i));
+
+ // No overlap.
+ assertFalse(rs.overlap(op));
+ assertFalse(op.overlap(rs));
+
+ // Partial overlap.
+ assertTrue(ir.overlap(rs));
+ assertTrue(rs.overlap(ir));
+
+ // Full overlap.
+ assertTrue(ir.overlap(op));
+ assertTrue(op.overlap(ir));
+ }
+}