Fix all lint and doc generators
- They now all go through the same logic which annotates
classes and methods based on what is supported.
Change-Id: Ib56038143aad62d645bd97af39857efa943af09d
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
index c093d39..fdc1f13 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
@@ -7,15 +7,8 @@
import com.android.tools.r8.StringResource;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
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.DirectMappedDexApplication;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
@@ -32,14 +25,9 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
-import java.util.function.Predicate;
public abstract class AbstractGenerateFiles {
@@ -53,6 +41,7 @@
final InternalOptions options = new InternalOptions(factory, reporter);
final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
+ final Path desugaredLibrarySpecificationPath;
final Collection<Path> desugaredLibraryImplementation;
final Path outputDirectory;
final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
@@ -71,6 +60,7 @@
Collection<Path> desugarImplementationPath,
Path outputDirectory)
throws Exception {
+ this.desugaredLibrarySpecificationPath = desugarConfigurationPath;
DesugaredLibrarySpecification specification =
readDesugaredLibraryConfiguration(desugarConfigurationPath);
Path androidJarPath = getAndroidJarPath(specification.getRequiredCompilationApiLevel());
@@ -81,10 +71,17 @@
if (!Files.isDirectory(this.outputDirectory)) {
throw new Exception("Output directory " + outputDirectory + " is not a directory");
}
-
- fillParallelMethods();
}
+ static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
+ String jar =
+ apiLevel == AndroidApiLevel.MASTER
+ ? "third_party/android_jar/lib-master/android.jar"
+ : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+ return Paths.get(jar);
+ }
+
+
private DesugaredLibrarySpecification readDesugaredLibraryConfiguration(
Path desugarConfigurationPath) {
return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
@@ -108,135 +105,7 @@
return app;
}
- private void fillParallelMethods() {
- DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;"));
- DexMethod parallelMethod =
- factory.createMethod(
- factory.collectionType,
- factory.createProto(streamType),
- factory.createString("parallelStream"));
- parallelMethods.add(parallelMethod);
- DexType baseStreamType =
- factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
- for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
- streamType =
- factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
- parallelMethod =
- factory.createMethod(
- streamType, factory.createProto(streamType), factory.createString("parallel"));
- parallelMethods.add(parallelMethod);
- // Also filter out the generated bridges for the covariant return type.
- parallelMethod =
- factory.createMethod(
- streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
- parallelMethods.add(parallelMethod);
- }
- }
-
- public static class SupportedMethods {
-
- public final Set<DexClass> classesWithAllMethodsSupported;
- public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
-
- public SupportedMethods(
- Set<DexClass> classesWithAllMethodsSupported,
- Map<DexClass, List<DexEncodedMethod>> supportedMethods) {
- this.classesWithAllMethodsSupported = classesWithAllMethodsSupported;
- this.supportedMethods = supportedMethods;
- }
- }
-
- SupportedMethods collectSupportedMethods(
- AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported) throws Exception {
-
- // Read the android.jar for the compilation API level. Read it as program instead of library
- // to get the local information for parameter names.
- AndroidApp library =
- AndroidApp.builder().addProgramFiles(getAndroidJarPath(compilationApiLevel)).build();
- DirectMappedDexApplication dexApplication =
- new ApplicationReader(library, options, Timing.empty()).read().toDirect();
-
- AndroidApp implementation =
- AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
- DirectMappedDexApplication implementationApplication =
- new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
-
- options.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
- List<DexMethod> backports =
- BackportedMethodRewriter.generateListOfBackportedMethods(
- implementation, options, ThreadUtils.getExecutorService(1));
-
- // Collect all the methods that the library desugar configuration adds support for.
- Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
- Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
- for (DexProgramClass clazz : dexApplication.classes()) {
- if (clazz.accessFlags.isPublic() && desugaredLibrarySpecification.isSupported(clazz.type)) {
- DexProgramClass implementationClass =
- implementationApplication.programDefinitionFor(clazz.getType());
- if (implementationClass == null) {
- throw new Exception("Implementation class not found for " + clazz.toSourceString());
- }
- boolean allMethodsAdded = true;
- for (DexEncodedMethod method : clazz.methods()) {
- if (!method.isPublic()) {
- continue;
- }
- ProgramMethod implementationMethod =
- implementationClass.lookupProgramMethod(method.getReference());
- // Don't include methods which are not implemented by the desugared library.
- if (supported.test(method)
- && (implementationMethod != null || backports.contains(method.getReference()))) {
- supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
- } else {
- allMethodsAdded = false;
- }
- }
- if (allMethodsAdded) {
- classesWithAllMethodsSupported.add(clazz);
- }
- }
-
- // All emulated interfaces static and default methods are supported.
- if (desugaredLibrarySpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
- assert clazz.isInterface();
- for (DexEncodedMethod method : clazz.methods()) {
- if (!method.isDefaultMethod() && !method.isStatic()) {
- continue;
- }
- if (supported.test(method)) {
- supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
- }
- }
- }
- }
-
- // All retargeted methods are supported.
- desugaredLibrarySpecification.forEachRetargetMethod(
- method -> {
- DexClass clazz = dexApplication.contextIndependentDefinitionFor(method.getHolderType());
- assert clazz != null;
- DexEncodedMethod encodedMethod = clazz.lookupMethod(method);
- if (encodedMethod == null) {
- // Some methods are registered but present higher in the hierarchy, ignore them.
- return;
- }
- if (supported.test(encodedMethod)) {
- supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(encodedMethod);
- }
- });
-
- return new SupportedMethods(classesWithAllMethodsSupported, supportedMethods);
- }
-
- static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
- String jar =
- apiLevel == AndroidApiLevel.MASTER
- ? "third_party/android_jar/lib-master/android.jar"
- : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
- return Paths.get(jar);
- }
-
- abstract void run() throws Exception;
+ abstract AndroidApiLevel run() throws Exception;
public static void main(String[] args) throws Exception {
if (args.length == 3) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
index 078a14a..a99db32 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -14,17 +14,27 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.PrintStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
+import java.util.Map;
+import java.util.TreeMap;
import java.util.stream.StreamSupport;
public class GenerateHtmlDoc extends AbstractGenerateFiles {
+ private static final String HTML_SPLIT = "<br> ";
+ private static final int MAX_LINE_CHARACTERS = 53;
+ private static final String SUP_1 = "<sup>1</sup>";
+ private static final String SUP_2 = "<sup>2</sup>";
+ private static final String SUP_3 = "<sup>3</sup>";
+ private static final String SUP_4 = "<sup>4</sup>";
+
public GenerateHtmlDoc(
String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
throws Exception {
@@ -82,17 +92,17 @@
private abstract static class SourceBuilder<B extends GenerateHtmlDoc.SourceBuilder> {
protected final DexClass clazz;
- protected final boolean newClass;
protected List<DexEncodedField> fields = new ArrayList<>();
- protected List<DexEncodedMethod> constructors = new ArrayList<>();
- protected List<DexEncodedMethod> methods = new ArrayList<>();
+ protected Map<DexEncodedMethod, MethodAnnotation> constructors =
+ new TreeMap<>(Comparator.comparing(DexEncodedMethod::getReference));
+ protected Map<DexEncodedMethod, MethodAnnotation> methods =
+ new TreeMap<>(Comparator.comparing(DexEncodedMethod::getReference));
String className;
String packageName;
- private SourceBuilder(DexClass clazz, boolean newClass) {
+ private SourceBuilder(DexClass clazz) {
this.clazz = clazz;
- this.newClass = newClass;
this.className = clazz.type.toSourceString();
int index = this.className.lastIndexOf('.');
this.packageName = index > 0 ? this.className.substring(0, index) : "";
@@ -105,32 +115,50 @@
return self();
}
- private B addMethod(DexEncodedMethod method) {
+ private B addMethod(DexEncodedMethod method, MethodAnnotation methodAnnotation) {
assert !method.isClassInitializer();
if (method.isInitializer()) {
- constructors.add(method);
+ constructors.put(method, methodAnnotation);
} else {
- methods.add(method);
+ methods.put(method, methodAnnotation);
}
return self();
}
+ // If we are in a.b.c, then anything starting with a.b should not be fully qualified.
+ protected String typeInPackageRecursive(String typeName, String packageName) {
+ String rewritten = typeInPackage(typeName, packageName);
+ if (rewritten != null) {
+ return rewritten;
+ }
+ String[] split = packageName.split("\\.");
+ if (split.length > 2) {
+ String prevPackage =
+ packageName.substring(0, packageName.length() - split[split.length - 1].length() - 1);
+ return typeInPackage(typeName, prevPackage);
+ }
+ return null;
+ }
+
protected String typeInPackage(String typeName, String packageName) {
if (typeName.startsWith(packageName)
&& typeName.length() > packageName.length()
- && typeName.charAt(packageName.length()) == '.'
- && typeName.indexOf('.', packageName.length() + 1) == -1) {
- return typeName.substring(packageName.length() + 1);
+ && typeName.charAt(packageName.length()) == '.') {
+ int last = typeName.lastIndexOf('.') + 1;
+ return typeName.substring(last);
}
return null;
}
protected String typeInPackage(String typeName) {
- String result = typeInPackage(typeName, packageName);
+ String result = typeInPackageRecursive(typeName, packageName);
if (result == null) {
result = typeInPackage(typeName, "java.lang");
}
if (result == null) {
+ result = typeInPackage(typeName, "java.util.function");
+ }
+ if (result == null) {
result = typeName;
}
return result.replace('$', '.');
@@ -271,7 +299,8 @@
}
HTMLBuilder appendTdPackage(String s) {
- appendLineStart("<td><code><em>" + s + "</em></code><br>");
+ String finalString = format(s, 4);
+ appendLineStart("<td><code><em>" + finalString + "</em></code><br>");
if (s.startsWith("java.time")) {
append("<a href=\"#java-time-customizations\">See customizations</a><br");
} else if (s.startsWith("java.nio")) {
@@ -280,10 +309,25 @@
return this;
}
+ private String format(String s, int i) {
+ String[] regexpSplit = s.split("\\.");
+ if (regexpSplit.length < i) {
+ return s;
+ }
+ int splitIndex = 0;
+ int mid = i / 2;
+ for (int j = 0; j < mid; j++) {
+ splitIndex += regexpSplit[j].length();
+ }
+ splitIndex += mid;
+ return s.substring(0, splitIndex) + HTML_SPLIT + s.substring(splitIndex);
+ }
+
HTMLBuilder appendTdClassName(String s) {
+ String finalString = format(s, 2);
appendLineEnd(
"<code><br><br><div style=\"font-size:small;font-weight:bold;\"> "
- + s
+ + finalString
+ "</div></code><br><br></td>");
return this;
}
@@ -298,6 +342,29 @@
return this;
}
+ HTMLBuilder appendMethodLiCode(String s) {
+ if (s.length() < MAX_LINE_CHARACTERS || s.contains("()")) {
+ return appendLiCode(s);
+ }
+ StringBuilder sb = new StringBuilder();
+ String[] split = s.split("\\(");
+ sb.append(split[0]).append('(').append(HTML_SPLIT);
+ if (split[1].length() < MAX_LINE_CHARACTERS - 2) {
+ sb.append(split[1]);
+ return appendLiCode(sb.toString());
+ }
+ String[] secondSplit = split[1].split(",");
+ sb.append(" ");
+ for (int i = 0; i < secondSplit.length; i++) {
+ sb.append(secondSplit[i]);
+ if (i != secondSplit.length - 1) {
+ sb.append(',');
+ sb.append(HTML_SPLIT);
+ }
+ }
+ return appendLiCode(sb.toString());
+ }
+
HTMLBuilder start(String tag) {
appendLine("<" + tag + ">");
increaseIndent();
@@ -313,11 +380,15 @@
public static class HTMLSourceBuilder extends SourceBuilder<HTMLSourceBuilder> {
- private final Set<DexMethod> parallelMethods;
+ private final ClassAnnotation classAnnotation;
+ private boolean parallelStreamMethod = false;
+ private boolean missingFromLatestAndroidJar = false;
+ private boolean unsupportedInMinApiRange = false;
+ private boolean covariantReturnSupported = false;
- public HTMLSourceBuilder(DexClass clazz, boolean newClass, Set<DexMethod> parallelMethods) {
- super(clazz, newClass);
- this.parallelMethods = parallelMethods;
+ public HTMLSourceBuilder(DexClass clazz, ClassAnnotation classAnnotation) {
+ super(clazz);
+ this.classAnnotation = classAnnotation;
}
@Override
@@ -325,6 +396,30 @@
return this;
}
+ private String getTextAnnotations(MethodAnnotation annotation) {
+ if (annotation == null) {
+ return "";
+ }
+ StringBuilder stringBuilder = new StringBuilder();
+ if (annotation.parallelStreamMethod) {
+ stringBuilder.append(SUP_1);
+ parallelStreamMethod = true;
+ }
+ if (annotation.missingFromLatestAndroidJar) {
+ stringBuilder.append(SUP_2);
+ missingFromLatestAndroidJar = true;
+ }
+ if (annotation.unsupportedInMinApiRange) {
+ stringBuilder.append(SUP_3);
+ unsupportedInMinApiRange = true;
+ }
+ if (annotation.covariantReturnSupported) {
+ stringBuilder.append(SUP_4);
+ covariantReturnSupported = true;
+ }
+ return stringBuilder.toString();
+ }
+
@Override
public String toString() {
HTMLBuilder builder = new HTMLBuilder();
@@ -339,7 +434,6 @@
"ul style=\"list-style-position:inside; list-style-type: none !important;"
+ " margin-left:0px;padding-left:0px !important;\"");
if (!fields.isEmpty()) {
- assert newClass; // Currently no fields are added to existing classes.
for (DexEncodedField field : fields) {
builder.appendLiCode(
accessFlags(field.accessFlags)
@@ -350,49 +444,65 @@
}
}
if (!constructors.isEmpty()) {
- for (DexEncodedMethod constructor : constructors) {
- builder.appendLiCode(
+ for (DexEncodedMethod constructor : constructors.keySet()) {
+ builder.appendMethodLiCode(
accessFlags(constructor.accessFlags)
+ " "
+ typeInPackage(className)
- + arguments(constructor));
+ + arguments(constructor)
+ + getTextAnnotations(constructors.get(constructor)));
}
}
- List<String> parallelM = new ArrayList<>();
if (!methods.isEmpty()) {
- for (DexEncodedMethod method : methods) {
- builder.appendLiCode(
+ for (DexEncodedMethod method : methods.keySet()) {
+ builder.appendMethodLiCode(
accessFlags(method.accessFlags)
+ " "
+ typeInPackage(method.getReference().proto.returnType)
+ " "
+ method.getReference().name
- + arguments(method));
- if (parallelMethods.contains(method.getReference())) {
- parallelM.add(method.getReference().name.toString());
- }
+ + arguments(method)
+ + getTextAnnotations(methods.get(method)));
}
}
builder.end("ul").end("td");
StringBuilder commentBuilder = new StringBuilder();
- if (newClass) {
- commentBuilder.append("Fully implemented class.");
- } else {
- commentBuilder.append("Additional methods on existing class.");
+ if (classAnnotation.fullySupported) {
+ commentBuilder.append("Fully implemented class.").append(HTML_SPLIT);
}
- if (!parallelM.isEmpty()) {
- commentBuilder.append(newClass ? "" : "<br>");
- if (parallelM.size() == 1) {
- commentBuilder
- .append("The method <code>")
- .append(parallelM.get(0))
- .append("</code> is only supported from API level 21.");
- } else {
- commentBuilder.append("The following methods are only supported from API level 21:<br>");
- for (int i = 0; i < parallelM.size(); i++) {
- commentBuilder.append("<code>").append(parallelM.get(i)).append("</code><br>");
- }
- }
+ if (parallelStreamMethod) {
+ commentBuilder
+ .append(SUP_1)
+ .append("Supported only on devices which API level is 21 or higher.")
+ .append(HTML_SPLIT);
+ }
+ if (missingFromLatestAndroidJar) {
+ commentBuilder
+ .append(SUP_2)
+ .append("Not present in Android ")
+ .append(MAX_TESTED_ANDROID_API_LEVEL)
+ .append(" (May not resolve at compilation).")
+ .append(HTML_SPLIT);
+ }
+ if (unsupportedInMinApiRange) {
+ commentBuilder
+ .append(SUP_3)
+ .append(" Not supported at all minSDK levels.")
+ .append(HTML_SPLIT);
+ }
+ if (covariantReturnSupported) {
+ commentBuilder
+ .append(SUP_4)
+ .append(" Also supported with covariant return type.")
+ .append(HTML_SPLIT);
+ }
+ if (!classAnnotation.unsupportedMethods.isEmpty()) {
+ commentBuilder
+ .append("Some methods (")
+ .append(classAnnotation.unsupportedMethods.size())
+ .append(") present in Android ")
+ .append(MAX_TESTED_ANDROID_API_LEVEL)
+ .append(" are not supported.");
}
builder.appendTdP(commentBuilder.toString());
builder.end("tr");
@@ -403,47 +513,43 @@
private void generateClassHTML(
PrintStream ps,
DexClass clazz,
- boolean newClass,
- Predicate<DexEncodedField> fieldsFilter,
- Predicate<DexEncodedMethod> methodsFilter) {
- SourceBuilder builder = new HTMLSourceBuilder(clazz, newClass, parallelMethods);
+ List<DexEncodedMethod> methods,
+ ClassAnnotation classAnnotation,
+ Map<DexMethod, MethodAnnotation> methodAnnotationMap) {
+ SourceBuilder<HTMLSourceBuilder> builder = new HTMLSourceBuilder(clazz, classAnnotation);
+ // We need to extend to support fields.
StreamSupport.stream(clazz.fields().spliterator(), false)
- .filter(fieldsFilter)
.filter(field -> field.accessFlags.isPublic() || field.accessFlags.isProtected())
.sorted(Comparator.comparing(DexEncodedField::toSourceString))
.forEach(builder::addField);
- StreamSupport.stream(clazz.methods().spliterator(), false)
- .filter(methodsFilter)
+ methods.stream()
.filter(
method ->
(method.accessFlags.isPublic() || method.accessFlags.isProtected())
&& !method.accessFlags.isBridge())
.sorted(Comparator.comparing(DexEncodedMethod::toSourceString))
- .forEach(builder::addMethod);
+ .forEach(m -> builder.addMethod(m, methodAnnotationMap.get(m.getReference())));
ps.println(builder);
}
- void run() throws Exception {
+ AndroidApiLevel run() throws Exception {
PrintStream ps = new PrintStream(Files.newOutputStream(outputDirectory.resolve("apis.html")));
- // Full classes added.
- SupportedMethods supportedMethods =
- collectSupportedMethods(MAX_TESTED_ANDROID_API_LEVEL, x -> true);
- supportedMethods.classesWithAllMethodsSupported.stream()
- .sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
- .forEach(clazz -> generateClassHTML(ps, clazz, true, field -> true, method -> true));
- // Methods added to existing classes.
- supportedMethods.supportedMethods.keySet().stream()
- .filter(clazz -> !supportedMethods.classesWithAllMethodsSupported.contains(clazz))
- .sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
- .forEach(
- clazz ->
- generateClassHTML(
- ps,
- clazz,
- false,
- field -> false,
- method -> supportedMethods.supportedMethods.get(clazz).contains(method)));
+ SupportedMethodsWithAnnotations supportedMethods =
+ new SupportedMethodsGenerator(options)
+ .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+
+ // Full classes added.
+ supportedMethods.supportedMethods.forEach(
+ (clazz, methods) -> {
+ generateClassHTML(
+ ps,
+ clazz,
+ methods,
+ supportedMethods.annotatedClasses.get(clazz.type),
+ supportedMethods.annotatedMethods);
+ });
+ return MAX_TESTED_ANDROID_API_LEVEL;
}
public static void main(String[] args) throws Exception {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index ad60d69..a08eb8a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
@@ -42,8 +43,6 @@
import java.util.Comparator;
import java.util.List;
import java.util.Set;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
public class GenerateLintFiles extends AbstractGenerateFiles {
@@ -151,7 +150,7 @@
private void writeLintFiles(
AndroidApiLevel compilationApiLevel,
AndroidApiLevel minApiLevel,
- SupportedMethods supportedMethods)
+ SupportedMethodsWithAnnotations supportedMethods)
throws Exception {
// Build a plain text file with the desugared APIs.
List<String> desugaredApisSignatures = new ArrayList<>();
@@ -161,17 +160,21 @@
(clazz, methods) -> {
String classBinaryName =
DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
- if (!supportedMethods.classesWithAllMethodsSupported.contains(clazz)) {
+ if (!supportedMethods.annotatedClasses.get(clazz.type).fullySupported) {
for (DexEncodedMethod method : methods) {
if (method.isInstanceInitializer() || method.isClassInitializer()) {
// No new constructors are added.
continue;
}
- desugaredApisSignatures.add(
- classBinaryName
- + '#'
- + method.getReference().name
- + method.getReference().proto.toDescriptorString());
+ MethodAnnotation methodAnnotation =
+ supportedMethods.annotatedMethods.get(method.getReference());
+ if (shouldAddMethodToLint(methodAnnotation, minApiLevel)) {
+ desugaredApisSignatures.add(
+ classBinaryName
+ + '#'
+ + method.getReference().name
+ + method.getReference().proto.toDescriptorString());
+ }
}
} else {
desugaredApisSignatures.add(classBinaryName);
@@ -198,49 +201,43 @@
consumer.finished(options.reporter);
}
+ private boolean shouldAddMethodToLint(
+ MethodAnnotation methodAnnotation, AndroidApiLevel minApiLevel) {
+ if (methodAnnotation == null) {
+ return true;
+ }
+ if (methodAnnotation.unsupportedInMinApiRange) {
+ // Do not lint method which are unavailable with some min apis.
+ return false;
+ }
+ if (methodAnnotation.parallelStreamMethod) {
+ return minApiLevel == AndroidApiLevel.L;
+ }
+ assert methodAnnotation.missingFromLatestAndroidJar;
+ // We add missing methods from the latest jar, javac will add the squikles.
+ return true;
+ }
+
private void generateLintFiles(
AndroidApiLevel compilationApiLevel,
- Predicate<AndroidApiLevel> generateForThisMinApiLevel,
- BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel)
+ AndroidApiLevel minApiLevel,
+ SupportedMethodsWithAnnotations supportedMethods)
throws Exception {
System.out.print(" - generating for min API:");
- for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
- if (!generateForThisMinApiLevel.test(minApiLevel)) {
- continue;
- }
-
- System.out.print(" " + minApiLevel);
-
- SupportedMethods supportedMethods =
- collectSupportedMethods(
- compilationApiLevel, (method -> supportedForMinApiLevel.test(minApiLevel, method)));
- writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
- }
- System.out.println();
+ System.out.print(" " + minApiLevel);
+ writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
}
- void run() throws Exception {
- // Run over all the API levels that the desugared library can be compiled with.
- for (int apiLevel = MAX_TESTED_ANDROID_API_LEVEL.getLevel();
- apiLevel >= desugaredLibrarySpecification.getRequiredCompilationApiLevel().getLevel();
- apiLevel--) {
- System.out.println("Generating lint files for compile API " + apiLevel);
- 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.getReference());
- });
+ public AndroidApiLevel run() throws Exception {
+ AndroidApiLevel compilationLevel =
+ desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+ SupportedMethodsWithAnnotations supportedMethods =
+ new SupportedMethodsGenerator(options)
+ .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+ System.out.println("Generating lint files for compile API " + compilationLevel);
+ generateLintFiles(compilationLevel, AndroidApiLevel.B, supportedMethods);
+ generateLintFiles(compilationLevel, AndroidApiLevel.L, supportedMethods);
+ return compilationLevel;
}
public static void main(String[] args) throws Exception {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
new file mode 100644
index 0000000..c196fe9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
@@ -0,0 +1,371 @@
+// Copyright (c) 2023, 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.ir.desugar.desugaredlibrary.lint;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.lint.AbstractGenerateFiles.MAX_TESTED_ANDROID_API_LEVEL;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+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.DirectMappedDexApplication;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.shaking.MainDexInfo;
+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.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+public class SupportedMethodsGenerator {
+
+ private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
+
+ private final InternalOptions options;
+
+ public SupportedMethodsGenerator(InternalOptions options) {
+ this.options = options;
+ }
+
+ public SupportedMethodsWithAnnotations run(
+ Collection<Path> desugaredLibraryImplementation, Path specification) throws IOException {
+ SupportedMethodsWithAnnotations.Builder builder = SupportedMethodsWithAnnotations.builder();
+ // First analyze everything which is supported when desugaring for api 1.
+ collectSupportedMethodsInB(desugaredLibraryImplementation, specification, builder);
+ // Second annotate all apis which are partially and/or fully supported.
+ AndroidApp library =
+ AndroidApp.builder()
+ .addProgramFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL))
+ .build();
+ DirectMappedDexApplication appForMax =
+ new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+ annotateMethodsNotOnLatestAndroidJar(appForMax, builder);
+ annotateParallelMethods(builder);
+ annotatePartialDesugaringMethods(builder, specification);
+ annotateClasses(builder, appForMax);
+ return builder.build();
+ }
+
+ private void annotateClasses(
+ SupportedMethodsWithAnnotations.Builder builder, DirectMappedDexApplication appForMax) {
+
+ builder.forEachClassAndMethods(
+ (clazz, methods) -> {
+ DexClass maxClass = appForMax.definitionFor(clazz.type);
+ List<DexMethod> missing = new ArrayList<>();
+ boolean fullySupported = true;
+ for (DexEncodedMethod method : maxClass.methods()) {
+ if (!(method.isPublic() || method.isProtectedMethod())) {
+ continue;
+ }
+ // If the method is in android.jar but not in the desugared library, or annotated, then
+ // the class is not marked as fully supported.
+ if (methods.stream().noneMatch(em -> em.getReference() == method.getReference())) {
+ missing.add(method.getReference());
+ fullySupported = false;
+ }
+ if (builder.annotatedMethods.containsKey(method.getReference())
+ && builder.annotatedMethods.get(method.getReference())
+ != MethodAnnotation.getCovariantReturnSupported()) {
+ fullySupported = false;
+ }
+ }
+ builder.annotateClass(clazz.type, new ClassAnnotation(fullySupported, missing));
+ });
+ }
+
+ private void annotatePartialDesugaringMethods(
+ SupportedMethodsWithAnnotations.Builder builder, Path specification) throws IOException {
+ for (int api = AndroidApiLevel.K.getLevel();
+ api <= MAX_TESTED_ANDROID_API_LEVEL.getLevel();
+ api++) {
+ if (api == 20) {
+ // Missing android.jar.
+ continue;
+ }
+ AndroidApiLevel androidApiLevel = AndroidApiLevel.getAndroidApiLevel(api);
+ AndroidApp library =
+ AndroidApp.builder().addProgramFiles(getAndroidJarPath(androidApiLevel)).build();
+ DirectMappedDexApplication dexApplication =
+ new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+ AppInfoWithClassHierarchy appInfo =
+ AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+ dexApplication,
+ ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+ MainDexInfo.none(),
+ GlobalSyntheticsStrategy.forNonSynthesizing(),
+ StartupOrder.empty());
+ MachineDesugaredLibrarySpecification machineSpecification =
+ getMachineSpecification(androidApiLevel, specification);
+
+ options.setMinApiLevel(androidApiLevel);
+ options.setDesugaredLibrarySpecification(machineSpecification);
+ List<DexMethod> backports =
+ BackportedMethodRewriter.generateListOfBackportedMethods(
+ library, options, ThreadUtils.getExecutorService(1));
+
+ int finalApi = api;
+ builder.forEachClassAndMethod(
+ (clazz, encodedMethod) -> {
+ DexMethod dexMethod = encodedMethod.getReference();
+ if (machineSpecification.isSupported(dexMethod)
+ || backports.contains(dexMethod)
+ || machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
+ if (machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
+ builder.annotateMethod(dexMethod, MethodAnnotation.getCovariantReturnSupported());
+ }
+ return;
+ }
+ if (machineSpecification.getEmulatedInterfaces().containsKey(dexMethod.getHolderType())
+ && encodedMethod.isStatic()) {
+ // Static methods on emulated interfaces are always supported if the emulated
+ // interface is
+ // supported.
+ return;
+ }
+ MethodResolutionResult methodResolutionResult =
+ appInfo.resolveMethod(
+ dexMethod,
+ appInfo
+ .contextIndependentDefinitionFor(dexMethod.getHolderType())
+ .isInterface());
+ if (methodResolutionResult.isFailedResolution()) {
+ builder.annotateMethod(dexMethod, MethodAnnotation.createMissingInMinApi(finalApi));
+ }
+ });
+ }
+ }
+
+ private void annotateParallelMethods(SupportedMethodsWithAnnotations.Builder builder) {
+ for (DexMethod parallelMethod : getParallelMethods()) {
+ builder.annotateMethod(parallelMethod, MethodAnnotation.getParallelStreamMethod());
+ }
+ }
+
+ private void annotateMethodsNotOnLatestAndroidJar(
+ DirectMappedDexApplication appForMax, SupportedMethodsWithAnnotations.Builder builder) {
+ builder.forEachClassAndMethod(
+ (clazz, method) -> {
+ DexClass dexClass = appForMax.definitionFor(clazz.type);
+ assert dexClass != null;
+ if (dexClass.lookupMethod(method.getReference()) == null) {
+ builder.annotateMethod(
+ method.getReference(), MethodAnnotation.getMissingFromLatestAndroidJar());
+ }
+ });
+ }
+
+ static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
+ String jar =
+ apiLevel == AndroidApiLevel.MASTER
+ ? "third_party/android_jar/lib-master/android.jar"
+ : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+ return Paths.get(jar);
+ }
+
+ private void collectSupportedMethodsInB(
+ Collection<Path> desugaredLibraryImplementation,
+ Path specification,
+ SupportedMethodsWithAnnotations.Builder builder)
+ throws IOException {
+
+ AndroidApp implementation =
+ AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
+ DirectMappedDexApplication implementationApplication =
+ new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
+
+ AndroidApp library =
+ AndroidApp.builder()
+ .addLibraryFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL))
+ .build();
+ DirectMappedDexApplication amendedAppForMax =
+ new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+
+ MachineDesugaredLibrarySpecification machineSpecification =
+ getMachineSpecification(AndroidApiLevel.B, specification);
+
+ options.setMinApiLevel(AndroidApiLevel.B);
+ options.setDesugaredLibrarySpecification(machineSpecification);
+ List<DexMethod> backports =
+ BackportedMethodRewriter.generateListOfBackportedMethods(
+ library, options, ThreadUtils.getExecutorService(1));
+
+ DesugaredLibraryAmender.run(
+ machineSpecification.getAmendLibraryMethods(),
+ machineSpecification.getAmendLibraryFields(),
+ amendedAppForMax,
+ options.reporter,
+ ComputedApiLevel.unknown());
+
+ for (DexProgramClass clazz : implementationApplication.classes()) {
+ // All emulated interfaces static and default methods are supported.
+ if (machineSpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
+ assert clazz.isInterface();
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (!method.isDefaultMethod() && !method.isStatic()) {
+ continue;
+ }
+ if (method.getName().startsWith("lambda$")
+ || method.getName().toString().contains("$deserializeLambda$")) {
+ // We don't care if lambda methods are present or not.
+ continue;
+ }
+ if (method
+ .getReference()
+ .toSourceString()
+ .equals("void java.util.Collection.forEach(java.util.function.Consumer)")) {
+ // This method is present for binary compatibility. Do not mark as supported (Supported
+ // through Iterable#forEach).
+ continue;
+ }
+ builder.addSupportedMethod(clazz, method);
+ }
+ addBackports(clazz, backports, builder, amendedAppForMax);
+ } else {
+ // All methods in maintained or rewritten classes are supported.
+ if ((clazz.accessFlags.isPublic() || clazz.accessFlags.isProtected())
+ && machineSpecification.isContextTypeMaintainedOrRewritten(clazz.type)
+ && amendedAppForMax.definitionFor(clazz.type) != null) {
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (!method.isPublic() && !method.isProtectedMethod()) {
+ continue;
+ }
+ builder.addSupportedMethod(clazz, method);
+ }
+ addBackports(clazz, backports, builder, amendedAppForMax);
+ }
+ }
+ }
+
+ // All retargeted methods are supported.
+ machineSpecification.forEachRetargetMethod(
+ method -> {
+ DexClass dexClass = implementationApplication.definitionFor(method.getHolderType());
+ if (dexClass != null) {
+ DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
+ if (dexEncodedMethod != null) {
+ builder.addSupportedMethod(dexClass, dexEncodedMethod);
+ return;
+ }
+ }
+ dexClass = amendedAppForMax.definitionFor(method.getHolderType());
+ DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
+ assert dexEncodedMethod != null;
+ builder.addSupportedMethod(dexClass, dexEncodedMethod);
+ });
+ }
+
+ private void addBackports(
+ DexProgramClass clazz,
+ List<DexMethod> backports,
+ SupportedMethodsWithAnnotations.Builder builder,
+ DirectMappedDexApplication amendedAppForMax) {
+ for (DexMethod backport : backports) {
+ if (clazz.type == backport.getHolderType()) {
+ DexClass maxClass = amendedAppForMax.definitionFor(clazz.type);
+ DexEncodedMethod dexEncodedMethod = maxClass.lookupMethod(backport);
+ // There is a single backport not in amendedAppForMax, Stream#ofNullable.
+ assert dexEncodedMethod != null
+ || backport
+ .toString()
+ .equals(
+ "java.util.stream.Stream java.util.stream.Stream.ofNullable(java.lang.Object)");
+ if (dexEncodedMethod == null) {
+ dexEncodedMethod =
+ DexEncodedMethod.builder()
+ .setMethod(backport)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_STATIC, false))
+ .build();
+ }
+ builder.addSupportedMethod(clazz, dexEncodedMethod);
+ }
+ }
+ }
+
+ private MachineDesugaredLibrarySpecification getMachineSpecification(
+ AndroidApiLevel api, Path specification) throws IOException {
+ DesugaredLibrarySpecification librarySpecification =
+ DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
+ StringResource.fromFile(specification),
+ options.itemFactory,
+ options.reporter,
+ false,
+ api.getLevel());
+ Path androidJarPath = getAndroidJarPath(librarySpecification.getRequiredCompilationApiLevel());
+ DexApplication app = createLoadingApp(androidJarPath, options);
+ return librarySpecification.toMachineSpecification(app, Timing.empty());
+ }
+
+ private DexApplication createLoadingApp(Path androidLib, InternalOptions options)
+ throws IOException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
+ ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
+ ExecutorService executorService = ThreadUtils.getExecutorService(options);
+ assert !options.ignoreJavaLibraryOverride;
+ options.ignoreJavaLibraryOverride = true;
+ DexApplication loadingApp = applicationReader.read(executorService);
+ options.ignoreJavaLibraryOverride = false;
+ return loadingApp;
+ }
+
+ private Set<DexMethod> getParallelMethods() {
+ Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
+ DexItemFactory factory = options.dexItemFactory();
+ DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;"));
+ DexMethod parallelMethod =
+ factory.createMethod(
+ factory.collectionType,
+ factory.createProto(streamType),
+ factory.createString("parallelStream"));
+ parallelMethods.add(parallelMethod);
+ DexType baseStreamType =
+ factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
+ for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
+ streamType =
+ factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
+ parallelMethod =
+ factory.createMethod(
+ streamType, factory.createProto(streamType), factory.createString("parallel"));
+ parallelMethods.add(parallelMethod);
+ // Also filter out the generated bridges for the covariant return type.
+ parallelMethod =
+ factory.createMethod(
+ streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
+ parallelMethods.add(parallelMethod);
+ }
+ return parallelMethods;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java
new file mode 100644
index 0000000..0cd6887
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsWithAnnotations.java
@@ -0,0 +1,215 @@
+// Copyright (c) 2023, 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.ir.desugar.desugaredlibrary.lint;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+public class SupportedMethodsWithAnnotations {
+
+ public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
+ public final Map<DexMethod, MethodAnnotation> annotatedMethods;
+ // A fully supported class has no annotated methods, and all the methods from the latest
+ // android.jar are supported.
+ public final Map<DexType, ClassAnnotation> annotatedClasses;
+
+ SupportedMethodsWithAnnotations(
+ Map<DexClass, List<DexEncodedMethod>> supportedMethods,
+ Map<DexMethod, MethodAnnotation> annotatedMethods,
+ Map<DexType, ClassAnnotation> annotatedClasses) {
+ this.supportedMethods = supportedMethods;
+ this.annotatedMethods = annotatedMethods;
+ this.annotatedClasses = annotatedClasses;
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ static class Builder {
+
+ Map<DexClass, List<DexEncodedMethod>> supportedMethods = new IdentityHashMap<>();
+ Map<DexMethod, MethodAnnotation> annotatedMethods = new IdentityHashMap<>();
+ Map<DexType, ClassAnnotation> annotatedClasses = new IdentityHashMap<>();
+
+ void forEachClassAndMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
+ supportedMethods.forEach(biConsumer);
+ }
+
+ void forEachClassAndMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
+ supportedMethods.forEach(
+ (clazz, methods) -> {
+ for (DexEncodedMethod method : methods) {
+ biConsumer.accept(clazz, method);
+ }
+ });
+ }
+
+ void addSupportedMethod(DexClass holder, DexEncodedMethod method) {
+ List<DexEncodedMethod> methods =
+ supportedMethods.computeIfAbsent(holder, f -> new ArrayList<>());
+ methods.add(method);
+ }
+
+ void annotateClass(DexType type, ClassAnnotation annotation) {
+ annotatedClasses.put(type, annotation);
+ }
+
+ void annotateMethod(DexMethod method, MethodAnnotation annotation) {
+ MethodAnnotation prev = annotatedMethods.getOrDefault(method, MethodAnnotation.getDefault());
+ annotatedMethods.put(method, annotation.combine(prev));
+ }
+
+ SupportedMethodsWithAnnotations build() {
+ supportedMethods.forEach(
+ (k, v) -> v.sort(Comparator.comparing(DexEncodedMember::getReference)));
+ return new SupportedMethodsWithAnnotations(
+ ImmutableSortedMap.copyOf(supportedMethods, Comparator.comparing(DexClass::getType)),
+ ImmutableMap.copyOf(annotatedMethods),
+ ImmutableMap.copyOf(annotatedClasses));
+ }
+ }
+
+ static class ClassAnnotation {
+
+ final boolean fullySupported;
+ // Methods in latest android.jar but unsupported.
+ final List<DexMethod> unsupportedMethods;
+
+ public ClassAnnotation(boolean fullySupported, List<DexMethod> unsupportedMethods) {
+ this.fullySupported = fullySupported;
+ unsupportedMethods.sort(Comparator.naturalOrder());
+ this.unsupportedMethods = ImmutableList.copyOf(unsupportedMethods);
+ }
+ }
+
+ public static class MethodAnnotation {
+
+ private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
+ new MethodAnnotation(false, false, true, false, -1, -1);
+ private static final MethodAnnotation DEFAULT =
+ new MethodAnnotation(false, false, false, false, -1, -1);
+ private static final MethodAnnotation PARALLEL_STREAM_METHOD =
+ new MethodAnnotation(true, false, false, false, -1, -1);
+ private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
+ new MethodAnnotation(false, true, false, false, -1, -1);
+
+ // ParallelStream methods are not supported when the runtime api level is strictly below 21.
+ final boolean parallelStreamMethod;
+ // Methods not in the latest android jar but still fully supported.
+ final boolean missingFromLatestAndroidJar;
+ // Methods not supported in a given min api range.
+ final boolean unsupportedInMinApiRange;
+ final boolean covariantReturnSupported;
+ final int minRange;
+ final int maxRange;
+
+ MethodAnnotation(
+ boolean parallelStreamMethod,
+ boolean missingFromLatestAndroidJar,
+ boolean covariantReturnSupported,
+ boolean unsupportedInMinApiRange,
+ int minRange,
+ int maxRange) {
+ this.parallelStreamMethod = parallelStreamMethod;
+ this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
+ this.covariantReturnSupported = covariantReturnSupported;
+ this.unsupportedInMinApiRange = unsupportedInMinApiRange;
+ this.minRange = minRange;
+ this.maxRange = maxRange;
+ }
+
+ public static MethodAnnotation getCovariantReturnSupported() {
+ return COVARIANT_RETURN_SUPPORTED;
+ }
+
+ public static MethodAnnotation getDefault() {
+ return DEFAULT;
+ }
+
+ public static MethodAnnotation getParallelStreamMethod() {
+ return PARALLEL_STREAM_METHOD;
+ }
+
+ public static MethodAnnotation getMissingFromLatestAndroidJar() {
+ return MISSING_FROM_LATEST_ANDROID_JAR;
+ }
+
+ public static MethodAnnotation createMissingInMinApi(int api) {
+ return new MethodAnnotation(false, false, false, true, api, api);
+ }
+
+ public boolean isUnsupportedInMinApiRange() {
+ return unsupportedInMinApiRange;
+ }
+
+ public int getMinRange() {
+ return minRange;
+ }
+
+ public int getMaxRange() {
+ return maxRange;
+ }
+
+ public boolean isCovariantReturnSupported() {
+ return covariantReturnSupported;
+ }
+
+ public MethodAnnotation combine(MethodAnnotation other) {
+ if (this == getDefault()) {
+ return other;
+ }
+ if (other == getDefault()) {
+ return this;
+ }
+ int newMin, newMax;
+ if (!unsupportedInMinApiRange && !other.unsupportedInMinApiRange) {
+ newMin = newMax = -1;
+ } else if (!unsupportedInMinApiRange || !other.unsupportedInMinApiRange) {
+ newMin = unsupportedInMinApiRange ? minRange : other.minRange;
+ newMax = unsupportedInMinApiRange ? maxRange : other.maxRange;
+ } else {
+ // Merge ranges if contiguous or throw.
+ if (maxRange == other.minRange - 1) {
+ newMin = minRange;
+ newMax = other.maxRange;
+ } else if (other.maxRange == minRange - 1) {
+ newMin = other.minRange;
+ newMax = maxRange;
+ } else {
+ // 20 is missing, so if maxRange or minRange are 19 the following is 21.
+ if (maxRange == 19 && other.minRange == 21) {
+ newMin = minRange;
+ newMax = other.maxRange;
+ } else if (other.maxRange == 19 && minRange == 21) {
+ newMin = other.minRange;
+ newMax = maxRange;
+ } else {
+ throw new RuntimeException("Cannot merge ranges.");
+ }
+ }
+ }
+ return new MethodAnnotation(
+ parallelStreamMethod || other.parallelStreamMethod,
+ missingFromLatestAndroidJar || other.missingFromLatestAndroidJar,
+ covariantReturnSupported || other.covariantReturnSupported,
+ unsupportedInMinApiRange || other.unsupportedInMinApiRange,
+ newMin,
+ newMax);
+ }
+ }
+}
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
index a54633b..a6a0535 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -498,10 +498,10 @@
libraryDesugaringSpecification.getSpecification(),
libraryDesugaringSpecification.getDesugarJdkLibs(),
out);
- desugaredApi.run(targetApi.getLevel());
+ AndroidApiLevel compileApi = desugaredApi.run();
return new CodeInspector(
- out.resolve("compile_api_level_" + targetApi.getLevel())
- .resolve("desugared_apis_" + targetApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
+ out.resolve("compile_api_level_" + compileApi.getLevel())
+ .resolve("desugared_apis_" + compileApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
}
private boolean addType(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index 55330bc..a29a6d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -5,10 +5,10 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
-import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8AndAll3Jdk11;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -20,11 +20,13 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateHtmlDoc;
import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateLintFiles;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -42,9 +44,11 @@
private List<String> lintContents;
- @Parameters(name = "{0}, spec: {1}")
+ @Parameters(name = "{1}")
public static List<Object[]> data() {
- return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8AndAll3Jdk11());
+ return buildParameters(
+ getTestParameters().withNoneRuntime().build(),
+ ImmutableList.of(JDK8, JDK11_MINIMAL, JDK11, JDK11_PATH, JDK11_LEGACY));
}
public LintFilesTest(
@@ -82,35 +86,18 @@
assertTrue(supportsAllMethodsOf("java/util/Optional"));
assertTrue(supportsAllMethodsOf("java/util/OptionalInt"));
- // No parallel* methods pre L, and all stream methods supported from L.
+ // No parallel* methods pre L, Stream are never fully supported due to takeWhile/dropWhile.
assertEquals(
minApiLevel == AndroidApiLevel.L,
supportsMethodButNotAllMethodsInClass(
"java/util/Collection#parallelStream()Ljava/util/stream/Stream;"));
assertEquals(
- minApiLevel == AndroidApiLevel.L, supportsAllMethodsOf("java/util/stream/DoubleStream"));
- assertFalse(
+ minApiLevel == AndroidApiLevel.L,
supportsMethodButNotAllMethodsInClass(
"java/util/stream/DoubleStream#parallel()Ljava/util/stream/DoubleStream;"));
- assertFalse(
- supportsMethodButNotAllMethodsInClass(
- "java/util/stream/DoubleStream#parallel()Ljava/util/stream/BaseStream;"));
- assertEquals(
- minApiLevel == AndroidApiLevel.B,
+ assertTrue(
supportsMethodButNotAllMethodsInClass(
"java/util/stream/DoubleStream#allMatch(Ljava/util/function/DoublePredicate;)Z"));
- assertEquals(
- minApiLevel == AndroidApiLevel.L, lintContents.contains("java/util/stream/IntStream"));
- assertFalse(
- supportsMethodButNotAllMethodsInClass(
- "java/util/stream/IntStream#parallel()Ljava/util/stream/IntStream;"));
- assertFalse(
- supportsMethodButNotAllMethodsInClass(
- "java/util/stream/IntStream#parallel()Ljava/util/stream/BaseStream;"));
- assertEquals(
- minApiLevel == AndroidApiLevel.B,
- supportsMethodButNotAllMethodsInClass(
- "java/util/stream/IntStream#allMatch(Ljava/util/function/IntPredicate;)Z"));
assertEquals(
libraryDesugaringSpecification != JDK8, supportsAllMethodsOf("java/util/concurrent/Flow"));
@@ -138,7 +125,7 @@
// Maintain type.
assertEquals(
- libraryDesugaringSpecification != JDK8,
+ libraryDesugaringSpecification != JDK8 && libraryDesugaringSpecification != JDK11_LEGACY,
supportsAllMethodsOf("java/io/UncheckedIOException"));
// Retarget method.
@@ -166,7 +153,7 @@
}
@Test
- public void testFileContent() throws Exception {
+ public void testLint() throws Exception {
Path directory = temp.newFolder().toPath();
Path jdkLibJar =
libraryDesugaringSpecification == JDK8
@@ -187,38 +174,36 @@
false,
AndroidApiLevel.B.getLevel());
- for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
- if (apiLevel.isGreaterThan(AndroidApiLevel.T)) {
- continue;
- }
- Path compileApiLevelDirectory = directory.resolve("compile_api_level_" + apiLevel.getLevel());
- if (apiLevel.getLevel()
- < desugaredLibrarySpecification.getRequiredCompilationApiLevel().getLevel()) {
- System.out.println("!Checking " + compileApiLevelDirectory);
- continue;
- }
- assertTrue(Files.exists(compileApiLevelDirectory));
- for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
- String desugaredApisBaseName =
- "desugared_apis_" + apiLevel.getLevel() + "_" + minApiLevel.getLevel();
- if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
- assertTrue(
- Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
- assertTrue(
- Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
- checkFileContent(
- minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
- } else {
- assertFalse(
- Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
- assertFalse(
- Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
- }
+ AndroidApiLevel requiredCompilationApiLevel =
+ desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+ Path compileApiLevelDirectory =
+ directory.resolve("compile_api_level_" + requiredCompilationApiLevel.getLevel());
+
+ assertTrue(Files.exists(compileApiLevelDirectory));
+ for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
+ String desugaredApisBaseName =
+ "desugared_apis_" + requiredCompilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
+ if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
+ assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+ assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
+ checkFileContent(
+ minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
+ } else {
+ assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+ assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
}
}
+ }
+
+ @Test
+ public void testHTML() throws Exception {
+ Path jdkLibJar =
+ libraryDesugaringSpecification == JDK8
+ ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
+ : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
Path directory2 = temp.newFolder().toPath();
- GenerateLintFiles.main(
+ GenerateHtmlDoc.main(
new String[] {
"--generate-api-docs",
libraryDesugaringSpecification.getSpecification().toString(),
@@ -231,7 +216,7 @@
assertEquals("<tr>", html.get(0));
assertEquals("</tr>", html.get(html.size() - 2));
if (libraryDesugaringSpecification == JDK11 || libraryDesugaringSpecification == JDK11_PATH) {
- assertEquals(7, html.stream().filter(s -> s.contains("Flow")).count());
+ assertEquals(6, html.stream().filter(s -> s.contains("Flow")).count());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
index 615e3d5..2b52f07 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -10,44 +10,19 @@
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8AndAll3Jdk11;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.StringResource;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.experimental.startup.StartupOrder;
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsGenerator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsWithAnnotations;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
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.HashSet;
-import java.util.IdentityHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -60,8 +35,6 @@
LibraryDesugaringSpecification librarySpecification;
- private DexApplication loadingApp = null;
-
@Parameters(name = "{0}, spec: {1}")
public static List<Object[]> data() {
return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8AndAll3Jdk11());
@@ -74,10 +47,14 @@
}
private static List<AndroidApiLevel> getRelevantApiLevels() {
+ // B is implicit - everything is supported on B.
return ImmutableList.of(
AndroidApiLevel.K,
+ AndroidApiLevel.L,
+ AndroidApiLevel.M,
AndroidApiLevel.N,
AndroidApiLevel.O,
+ AndroidApiLevel.P,
AndroidApiLevel.Q,
AndroidApiLevel.R,
AndroidApiLevel.S,
@@ -141,26 +118,29 @@
@Test
public void test() throws Exception {
- InternalOptions options = new InternalOptions(new DexItemFactory(), new Reporter());
+ SupportedMethodsWithAnnotations supportedMethods =
+ new SupportedMethodsGenerator(new InternalOptions())
+ .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification());
- Set<DexMethod> supportedMethodsInB = collectListOfSupportedMethods(AndroidApiLevel.B, options);
-
- Map<AndroidApiLevel, Set<DexMethod>> failures = new IdentityHashMap<>();
for (AndroidApiLevel api : getRelevantApiLevels()) {
- Set<DexMethod> dexMethods =
- verifyAllMethodsSupportedInBAreSupportedIn(supportedMethodsInB, api, options);
- failures.put(api, dexMethods);
+ Set<DexMethod> localFailures = Sets.newIdentityHashSet();
+ supportedMethods.annotatedMethods.forEach(
+ (method, annotation) -> {
+ if (annotation.isUnsupportedInMinApiRange()) {
+ if (api.getLevel() >= annotation.getMinRange()
+ && api.getLevel() <= annotation.getMaxRange()) {
+ localFailures.add(method);
+ }
+ }
+ });
+ Set<String> expectedFailures = getExpectedFailures(api);
+ Set<String> apiFailuresString =
+ localFailures.stream().map(DexMethod::toString).collect(Collectors.toSet());
+ if (!expectedFailures.equals(apiFailuresString)) {
+ System.out.println("Failure for api " + api);
+ assertEquals(expectedFailures, apiFailuresString);
+ }
}
- failures.forEach(
- (api, apiFailures) -> {
- Set<String> expectedFailures = getExpectedFailures(api);
- Set<String> apiFailuresString =
- apiFailures.stream().map(DexMethod::toString).collect(Collectors.toSet());
- if (!expectedFailures.equals(apiFailuresString)) {
- System.out.println("Failure for api " + api);
- assertEquals(expectedFailures, apiFailuresString);
- }
- });
}
private Set<String> getExpectedFailures(AndroidApiLevel api) {
@@ -183,6 +163,10 @@
&& api.isLessThan(AndroidApiLevel.T)) {
expectedFailures.addAll(FAILURES_TO_ARRAY);
}
+ if (librarySpecification == JDK8 && api.isLessThan(AndroidApiLevel.T)) {
+ // Interestingly that was added somehow to JDK8 desugared library at some point...
+ expectedFailures.addAll(FAILURES_TO_ARRAY);
+ }
if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
expectedFailures.addAll(FAILURES_CHRONOLOGY);
expectedFailures.addAll(FAILURES_DATE_TIME_BUILDER);
@@ -196,137 +180,4 @@
}
return expectedFailures;
}
-
- private Set<DexMethod> verifyAllMethodsSupportedInBAreSupportedIn(
- Set<DexMethod> supportedMethodsInB, AndroidApiLevel api, InternalOptions options)
- throws IOException {
- Set<DexMethod> failures = Sets.newIdentityHashSet();
- AndroidApp library =
- AndroidApp.builder().addProgramFiles(ToolHelper.getAndroidJar(api)).build();
- DirectMappedDexApplication dexApplication =
- new ApplicationReader(library, options, Timing.empty()).read().toDirect();
- AppInfoWithClassHierarchy appInfo =
- AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
- dexApplication,
- ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
- MainDexInfo.none(),
- GlobalSyntheticsStrategy.forNonSynthesizing(),
- StartupOrder.empty());
- MachineDesugaredLibrarySpecification machineSpecification =
- getMachineSpecification(api, options);
-
- options.setMinApiLevel(api);
- options.setDesugaredLibrarySpecification(machineSpecification);
- List<DexMethod> backports =
- BackportedMethodRewriter.generateListOfBackportedMethods(
- library, options, ThreadUtils.getExecutorService(1));
-
- for (DexMethod dexMethod : supportedMethodsInB) {
- if (machineSpecification.isSupported(dexMethod)) {
- continue;
- }
- if (backports.contains(dexMethod)) {
- continue;
- }
- if (machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
- continue;
- }
- if (machineSpecification.getEmulatedInterfaces().containsKey(dexMethod.getHolderType())) {
- // Static methods on emulated interfaces are always supported if the emulated interface is
- // supported.
- continue;
- }
- MethodResolutionResult methodResolutionResult =
- appInfo.resolveMethod(
- dexMethod,
- appInfo.contextIndependentDefinitionFor(dexMethod.getHolderType()).isInterface());
- if (methodResolutionResult.isFailedResolution()) {
- failures.add(dexMethod);
- }
- }
- return failures;
- }
-
- private MachineDesugaredLibrarySpecification getMachineSpecification(
- AndroidApiLevel api, InternalOptions options) throws IOException {
- DesugaredLibrarySpecification specification =
- DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
- StringResource.fromFile(librarySpecification.getSpecification()),
- options.itemFactory,
- options.reporter,
- false,
- api.getLevel());
- Path androidJarPath = ToolHelper.getAndroidJar(specification.getRequiredCompilationApiLevel());
- DexApplication app = getLoadingApp(androidJarPath, options);
- return specification.toMachineSpecification(app, Timing.empty());
- }
-
- private DexApplication getLoadingApp(Path androidLib, InternalOptions options)
- throws IOException {
- if (loadingApp != null) {
- return loadingApp;
- }
- AndroidApp.Builder builder = AndroidApp.builder();
- AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
- ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
- ExecutorService executorService = ThreadUtils.getExecutorService(options);
- assert !options.ignoreJavaLibraryOverride;
- options.ignoreJavaLibraryOverride = true;
- loadingApp = applicationReader.read(executorService);
- options.ignoreJavaLibraryOverride = false;
- return loadingApp;
- }
-
- private Set<DexMethod> collectListOfSupportedMethods(AndroidApiLevel api, InternalOptions options)
- throws Exception {
-
- AndroidApp implementation =
- AndroidApp.builder().addProgramFiles(librarySpecification.getDesugarJdkLibs()).build();
- DirectMappedDexApplication implementationApplication =
- new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
-
- AndroidApp library =
- AndroidApp.builder().addProgramFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)).build();
- DirectMappedDexApplication applicationForT =
- new ApplicationReader(library, options, Timing.empty()).read().toDirect();
-
- MachineDesugaredLibrarySpecification machineSpecification =
- getMachineSpecification(AndroidApiLevel.B, implementationApplication.options);
- Set<DexMethod> supportedMethods = Sets.newIdentityHashSet();
-
- for (DexProgramClass clazz : implementationApplication.classes()) {
- // All emulated interfaces static and default methods are supported.
- if (machineSpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
- assert clazz.isInterface();
- for (DexEncodedMethod method : clazz.methods()) {
- if (!method.isDefaultMethod() && !method.isStatic()) {
- continue;
- }
- if (method.getName().startsWith("lambda$")
- || method.getName().toString().contains("$deserializeLambda$")) {
- // We don't care if lambda methods are present or not.
- continue;
- }
- supportedMethods.add(method.getReference());
- }
- } else {
- // All methods in maintained or rewritten classes are supported.
- if ((clazz.accessFlags.isPublic() || clazz.accessFlags.isProtected())
- && machineSpecification.isContextTypeMaintainedOrRewritten(clazz.type)
- && applicationForT.definitionFor(clazz.type) != null) {
- for (DexEncodedMethod method : clazz.methods()) {
- if (!method.isPublic() && !method.isProtectedMethod()) {
- continue;
- }
- supportedMethods.add(method.getReference());
- }
- }
- }
- }
-
- // All retargeted methods are supported.
- machineSpecification.forEachRetargetMethod(supportedMethods::add);
-
- return supportedMethods;
- }
}