Introduce test in desugared lib resolving all invokes
- Ensure all desugared library invokes resolve or are
in an allowed white list
- Simplify and Fix ApiFlip conversions
- Fix desugared library flag
- Clean emulated interface to avoid dead code, removing
non resolving invokes and reducing L8 output code size.
Bug: b/244294200
Change-Id: I2295fe2b3ce1610c0069b7da29310eb5b7e69fa6
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
index 52d878b..089a293 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -12,7 +12,6 @@
public class CustomConversionAsmRewriteDescription {
static final String WRAP_CONVERT = "wrap_convert";
- static final String INVERTED_WRAP_CONVERT = "inverted_wrap_convert";
static final String CONVERT = "convert";
private static final Set<String> ENUM_WRAP_CONVERT_OWNER =
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
index c20de96..0848c09 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
@@ -6,7 +6,6 @@
import static desugaredlibrary.AsmRewriter.ASM_VERSION;
import static desugaredlibrary.CustomConversionAsmRewriteDescription.CONVERT;
-import static desugaredlibrary.CustomConversionAsmRewriteDescription.INVERTED_WRAP_CONVERT;
import static desugaredlibrary.CustomConversionAsmRewriteDescription.WRAP_CONVERT;
import static desugaredlibrary.CustomConversionAsmRewriter.CustomConversionVersion.LEGACY;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
@@ -133,51 +132,46 @@
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String descriptor, boolean isInterface) {
- if (opcode == INVOKESTATIC) {
- if (name.equals(WRAP_CONVERT)) {
- convertInvoke(
- opcode,
- owner,
- descriptor,
- isInterface,
- javaWrapConvertOwnerMap,
- j$WrapConvertOwnerMap);
- return;
- }
- if (name.equals(INVERTED_WRAP_CONVERT)) {
- convertInvoke(
- opcode,
- owner,
- descriptor,
- isInterface,
- j$WrapConvertOwnerMap,
- javaWrapConvertOwnerMap);
- return;
- }
+ if (opcode == INVOKESTATIC && name.equals(WRAP_CONVERT)) {
+ convertInvoke(opcode, owner, descriptor, isInterface);
+ return;
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
- private void convertInvoke(
- int opcode,
- String owner,
- String descriptor,
- boolean isInterface,
- Map<String, String> javaMap,
- Map<String, String> j$Map) {
- if (!javaMap.containsKey(owner)
- || !j$Map.containsKey(owner)
- || !(owner.startsWith("java") || owner.startsWith("j$"))) {
- throw new RuntimeException("Cannot transform wrap_convert method for " + owner);
+ private String extractFirstArg(String descriptor) {
+ assert descriptor.startsWith("(L");
+ int end = descriptor.indexOf(';');
+ assert end > 2;
+ return descriptor.substring(2, end);
+ }
+
+ private void convertInvoke(int opcode, String owner, String descriptor, boolean isInterface) {
+ String firstArg = extractFirstArg(descriptor);
+ assert sameBaseName(firstArg, owner);
+ if (!javaWrapConvertOwnerMap.containsKey(owner)
+ || !j$WrapConvertOwnerMap.containsKey(owner)
+ || !(firstArg.startsWith("java") || firstArg.startsWith("j$"))) {
+ throw new RuntimeException(
+ "Cannot transform wrap_convert method for " + firstArg + " (owner: " + owner + ")");
}
- if (owner.startsWith("java")) {
- String newOwner = j$Map.get(owner);
+ if (firstArg.startsWith("java")) {
+ String newOwner = javaWrapConvertOwnerMap.get(owner);
super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
return;
}
- assert owner.startsWith("j$");
- String newOwner = javaMap.get(owner);
+ assert firstArg.startsWith("j$");
+ String newOwner = j$WrapConvertOwnerMap.get(owner);
super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
}
+
+ private boolean sameBaseName(String firstArg, String owner) {
+ assert owner.startsWith("j$");
+ if (firstArg.equals(owner)) {
+ return true;
+ }
+ String javaName = owner.replace("j$", "java");
+ return firstArg.equals(javaName);
+ }
}
}
diff --git a/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java b/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
index 8db22b3..510d621 100644
--- a/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
+++ b/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
@@ -5,7 +5,7 @@
package j$.nio.file.spi;
public class FileSystemProvider {
- public static java.nio.file.spi.FileSystemProvider inverted_wrap_convert(
+ public static java.nio.file.spi.FileSystemProvider wrap_convert(
j$.nio.file.spi.FileSystemProvider provider) {
// Rewritten in ASM to the wrapper method.
return null;
diff --git a/src/library_desugar/java/j$/util/stream/Stream.java b/src/library_desugar/java/j$/util/stream/Stream.java
index dc5861d..7e06e4a 100644
--- a/src/library_desugar/java/j$/util/stream/Stream.java
+++ b/src/library_desugar/java/j$/util/stream/Stream.java
@@ -6,14 +6,6 @@
public class Stream<T> {
- public static java.util.stream.Stream<?> inverted_wrap_convert(j$.util.stream.Stream<?> stream) {
- return null;
- }
-
- public static j$.util.stream.Stream<?> inverted_wrap_convert(java.util.stream.Stream<?> stream) {
- return null;
- }
-
public static java.util.stream.Stream<?> wrap_convert(j$.util.stream.Stream<?> stream) {
return null;
}
diff --git a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
index 5be05a8..e34e571 100644
--- a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
+++ b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
@@ -29,7 +29,7 @@
Class.forName("java.nio.file.FileSystems");
j$.nio.file.FileSystem fileSystem = FileSystems.getDefault();
j$.nio.file.spi.FileSystemProvider provider = fileSystem.provider();
- return j$.nio.file.spi.FileSystemProvider.inverted_wrap_convert(provider);
+ return j$.nio.file.spi.FileSystemProvider.wrap_convert(provider);
} catch (ClassNotFoundException ignored) {
// We reach this path is API < 26.
}
diff --git a/src/library_desugar/java/java/util/stream/FlatMapApiFlips.java b/src/library_desugar/java/java/util/stream/FlatMapApiFlips.java
index b9e1b9e..ac81093 100644
--- a/src/library_desugar/java/java/util/stream/FlatMapApiFlips.java
+++ b/src/library_desugar/java/java/util/stream/FlatMapApiFlips.java
@@ -37,6 +37,7 @@
this.function = function;
}
+ @SuppressWarnings("unchecked")
private R flipStream(R maybeStream) {
if (maybeStream == null) {
return null;
@@ -89,6 +90,7 @@
this.function = function;
}
+ @SuppressWarnings("unchecked")
private R flipStream(R maybeStream) {
if (maybeStream == null) {
return null;
@@ -115,6 +117,7 @@
this.function = function;
}
+ @SuppressWarnings("unchecked")
private R flipStream(R maybeStream) {
if (maybeStream == null) {
return null;
@@ -143,6 +146,7 @@
this.function = function;
}
+ @SuppressWarnings("unchecked")
private R flipStream(R maybeStream) {
if (maybeStream == null) {
return null;
diff --git a/src/library_desugar/java/java/util/stream/StackWalkerApiFlips.java b/src/library_desugar/java/java/util/stream/StackWalkerApiFlips.java
index 300c5ad..0951704 100644
--- a/src/library_desugar/java/java/util/stream/StackWalkerApiFlips.java
+++ b/src/library_desugar/java/java/util/stream/StackWalkerApiFlips.java
@@ -22,17 +22,16 @@
this.function = function;
}
+ @SuppressWarnings("unchecked")
private T flipStream(T maybeStream) {
if (maybeStream == null) {
return null;
}
if (maybeStream instanceof java.util.stream.Stream<?>) {
- return (T)
- j$.util.stream.Stream.inverted_wrap_convert((java.util.stream.Stream<?>) maybeStream);
+ return (T) j$.util.stream.Stream.wrap_convert((java.util.stream.Stream<?>) maybeStream);
}
if (maybeStream instanceof j$.util.stream.Stream<?>) {
- return (T)
- j$.util.stream.Stream.inverted_wrap_convert((j$.util.stream.Stream<?>) maybeStream);
+ return (T) j$.util.stream.Stream.wrap_convert((j$.util.stream.Stream<?>) maybeStream);
}
throw exception("java.util.stream.Stream", maybeStream.getClass());
}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 5a1d513..9230e9b 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -390,7 +390,6 @@
"rewrite_prefix": {
"desugar.": "j$.desugar.",
"libcore.": "j$.libcore.",
- "java.lang.Desugar": "j$.lang.Desugar",
"sun.security.action.": "j$.sun.security.action."
}
},
@@ -450,6 +449,9 @@
},
"java.nio.file.Path": {
"j$.nio.file.Path": "java.nio.file.Path"
+ },
+ "java.nio.file.WatchEvent": {
+ "j$.nio.file.WatchEvent": "java.nio.file.WatchEvent"
}
},
"retarget_method": {
@@ -510,6 +512,9 @@
},
{
"api_level_below_or_equal": 18,
+ "rewrite_prefix": {
+ "java.lang.DesugarCharacter": "j$.lang.DesugarCharacter"
+ },
"retarget_static_field": {
"sun.nio.cs.US_ASCII sun.nio.cs.US_ASCII#INSTANCE": "java.nio.charset.Charset java.nio.charset.StandardCharsets#US_ASCII",
"sun.nio.cs.ISO_8859_1 sun.nio.cs.ISO_8859_1#INSTANCE": "java.nio.charset.Charset java.nio.charset.StandardCharsets#ISO_8859_1",
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index c764377..fd607ee 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.GenericSignature;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.utils.IterableUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -59,9 +60,8 @@
}
DexType newType = emulatedInterfaces.get(emulatedInterface.type);
assert newType != null;
- DexEncodedMethod[] newVirtualMethods =
- renameHolder(emulatedInterface.virtualMethods(), newType);
- DexEncodedMethod[] newDirectMethods = renameHolder(emulatedInterface.directMethods(), newType);
+ DexEncodedMethod[] newVirtualMethods = computeNewVirtualMethods(emulatedInterface, newType);
+ DexEncodedMethod[] newDirectMethods = DexEncodedMethod.EMPTY_ARRAY;
assert emulatedInterface.getSuperType() == appView.dexItemFactory().objectType;
assert !emulatedInterface.hasFields();
assert emulatedInterface.getNestHost() == null;
@@ -116,11 +116,24 @@
return newInterfaces;
}
- private DexEncodedMethod[] renameHolder(Iterable<DexEncodedMethod> methods, DexType newName) {
- List<DexEncodedMethod> methodArray = IterableUtils.toNewArrayList(methods);
+ private DexEncodedMethod[] computeNewVirtualMethods(
+ DexProgramClass emulatedInterface, DexType newName) {
+ Iterable<ProgramMethod> emulatedMethods =
+ emulatedInterface.virtualProgramMethods(
+ m ->
+ appView
+ .options()
+ .machineDesugaredLibrarySpecification
+ .getEmulatedInterfaceEmulatedDispatchMethodDescriptor(m.getReference())
+ != null);
+ List<ProgramMethod> methodArray = IterableUtils.toNewArrayList(emulatedMethods);
DexEncodedMethod[] newMethods = new DexEncodedMethod[methodArray.size()];
for (int i = 0; i < newMethods.length; i++) {
- newMethods[i] = methodArray.get(i).toRenamedHolderMethod(newName, appView.dexItemFactory());
+ newMethods[i] =
+ methodArray
+ .get(i)
+ .getDefinition()
+ .toRenamedHolderMethod(newName, appView.dexItemFactory());
}
return newMethods;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 2838f2c..c3243c0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -15,20 +15,39 @@
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.hamcrest.CoreMatchers;
import org.junit.Assume;
import org.junit.Test;
@@ -39,6 +58,30 @@
@RunWith(Parameterized.class)
public class DesugaredLibraryContentTest extends DesugaredLibraryTestBase {
+ // The class sun.misc.Unsafe is runnable on Android despite not being in android.jar.
+ private static final Set<String> ALLOWED_MISSING_HOLDER = ImmutableSet.of("sun.misc.Unsafe");
+ private static final Set<String> ALLOWED_MISSING_METHOD =
+ ImmutableSet.of(
+ // The takeWhile/dropWhile methods are present in the wrappers but not yet present on
+ // android.jar
+ // leading to NoSuchMethod errors, yet, we keep them for subsequent android versions.
+ "java.util.stream.IntStream"
+ + " java.util.stream.IntStream.dropWhile(java.util.function.IntPredicate)",
+ "java.util.stream.Stream java.util.stream.Stream.takeWhile(java.util.function.Predicate)",
+ "java.util.stream.LongStream"
+ + " java.util.stream.LongStream.dropWhile(java.util.function.LongPredicate)",
+ "java.util.stream.DoubleStream"
+ + " java.util.stream.DoubleStream.takeWhile(java.util.function.DoublePredicate)",
+ "java.util.stream.IntStream"
+ + " java.util.stream.IntStream.takeWhile(java.util.function.IntPredicate)",
+ "java.util.stream.Stream java.util.stream.Stream.dropWhile(java.util.function.Predicate)",
+ "java.util.stream.LongStream"
+ + " java.util.stream.LongStream.takeWhile(java.util.function.LongPredicate)",
+ "java.util.stream.DoubleStream"
+ + " java.util.stream.DoubleStream.dropWhile(java.util.function.DoublePredicate)",
+ // FileStore.getBlockSize() was added in 33 which confuses the required library (30).
+ "long java.nio.file.FileStore.getBlockSize()");
+
private final TestParameters parameters;
private final CompilationSpecification compilationSpecification;
private final LibraryDesugaringSpecification libraryDesugaringSpecification;
@@ -67,7 +110,63 @@
.apply(libraryDesugaringSpecification::configureL8TestBuilder)
.compile()
.assertNoMessages()
- .inspect(this::assertCorrect);
+ .inspect(this::assertCorrect)
+ .inspect(this::assertAllInvokeResolve);
+ }
+
+ private void assertAllInvokeResolve(CodeInspector inspector) throws IOException {
+ AndroidApp build =
+ AndroidApp.builder()
+ .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+ .build();
+ DirectMappedDexApplication libHolder =
+ new ApplicationReader(build, inspector.getApplication().options, Timing.empty())
+ .read()
+ .toDirect();
+ DirectMappedDexApplication finalApp =
+ inspector
+ .getApplication()
+ .toDirect()
+ .builder()
+ .replaceLibraryClasses(libHolder.libraryClasses())
+ .build();
+ AppInfoWithClassHierarchy appInfo =
+ AppView.createForD8(
+ AppInfo.createInitialAppInfo(
+ finalApp, GlobalSyntheticsStrategy.forNonSynthesizing()))
+ .appInfoForDesugaring();
+ Map<DexMethod, Object> failures = new IdentityHashMap<>();
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ for (FoundMethodSubject method : clazz.allMethods()) {
+ if (method.hasCode()) {
+ for (InstructionSubject instruction : method.instructions(InstructionSubject::isInvoke)) {
+ assertInvokeResolution(instruction, appInfo, method, failures);
+ }
+ }
+ }
+ }
+ for (DexMethod dexMethod : new HashSet<>(failures.keySet())) {
+ if (ALLOWED_MISSING_HOLDER.contains(dexMethod.getHolderType().toString())) {
+ failures.remove(dexMethod);
+ } else if (ALLOWED_MISSING_METHOD.contains(dexMethod.toString())) {
+ failures.remove(dexMethod);
+ }
+ }
+ assertTrue(failures.isEmpty());
+ }
+
+ private void assertInvokeResolution(
+ InstructionSubject instruction,
+ AppInfoWithClassHierarchy appInfo,
+ FoundMethodSubject context,
+ Map<DexMethod, Object> failures) {
+ DexMethod method = instruction.getMethod();
+ assert method != null;
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormatLegacy(method);
+ if (methodResolutionResult.isFailedResolution()) {
+ failures.put(method, new Pair(context, methodResolutionResult));
+ }
}
@Test
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index aeb419e..b569a57 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -178,6 +178,10 @@
}
}
+ public DexApplication getApplication() {
+ return application;
+ }
+
public DexItemFactory getFactory() {
return dexItemFactory;
}