Add a simple command line tool to get the list of backported APIs

Bug: 140367927
Change-Id: Ib6580a71738fefa76de0458330e32df71c85f032
diff --git a/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java b/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
new file mode 100644
index 0000000..90f577b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class GenerateBackportedMethodList {
+
+  public static void main(String[] args) {
+    BackportedMethodRewriter.generateListOfBackportedMethods(AndroidApiLevel.B).stream()
+        .map(DexMethod::toSourceString)
+        .sorted()
+        .forEach(System.out::println);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index df3b9b7..14dbbbd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -59,6 +59,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 public final class BackportedMethodRewriter {
 
@@ -78,7 +79,7 @@
     this.appView = appView;
     this.converter = converter;
     this.factory = appView.dexItemFactory();
-    this.rewritableMethods = new RewritableMethods(appView);
+    this.rewritableMethods = new RewritableMethods(appView.options(), appView);
     Map<String, String> backportCoreLibraryMembers =
         appView.options().desugaredLibraryConfiguration.getBackportCoreLibraryMember();
     for (String coreLibMember : backportCoreLibraryMembers.keySet()) {
@@ -91,6 +92,16 @@
     }
   }
 
+  public static List<DexMethod> generateListOfBackportedMethods(AndroidApiLevel apiLevel) {
+    List<DexMethod> methods = new ArrayList<>();
+    InternalOptions options = new InternalOptions();
+    options.minApiLevel = apiLevel.getLevel();
+    BackportedMethodRewriter.RewritableMethods rewritableMethods =
+        new BackportedMethodRewriter.RewritableMethods(options, null);
+    rewritableMethods.visit(methods::add);
+    return methods;
+  }
+
   public void desugar(IRCode code) {
     if (rewritableMethods.isEmpty()) {
       return; // Nothing to do!
@@ -230,9 +241,8 @@
     // Map backported method to a provider for creating the actual target method (with code).
     private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
 
-    public RewritableMethods(AppView<?> appView) {
-      InternalOptions options = appView.options();
-      DexItemFactory factory = appView.dexItemFactory();
+    public RewritableMethods(InternalOptions options, AppView<?> appView) {
+      DexItemFactory factory = options.itemFactory;
 
       if (options.minApiLevel < AndroidApiLevel.K.getLevel()) {
         initializeAndroidKMethodProviders(factory);
@@ -266,6 +276,10 @@
       return rewritable.isEmpty();
     }
 
+    public void visit(Consumer<DexMethod> consumer) {
+      rewritable.keySet().forEach(consumer);
+    }
+
     private void initializeAndroidKMethodProviders(DexItemFactory factory) {
       // Note: Long.compare rewriting is handled by CodeRewriter since there is a dedicated
       // bytecode which supports the operation.
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index efe42a1..f8ee25b 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -695,11 +695,20 @@
     return getAndroidJar(AndroidApiLevel.getAndroidApiLevel(apiLevel));
   }
 
-  public static Path getAndroidJar(AndroidApiLevel apiLevel) {
+  private static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
     String jar = String.format(
         ANDROID_JAR_PATTERN,
         (apiLevel == AndroidApiLevel.getDefault() ? DEFAULT_MIN_SDK : apiLevel).getLevel());
-    Path path = Paths.get(jar);
+    return Paths.get(jar);
+  }
+
+  public static boolean hasAndroidJar(AndroidApiLevel apiLevel) {
+    Path path = getAndroidJarPath(apiLevel);
+    return Files.exists(path);
+  }
+
+  public static Path getAndroidJar(AndroidApiLevel apiLevel) {
+    Path path = getAndroidJarPath(apiLevel);
     assert Files.exists(path)
         : "Expected android jar to exist for API level " + apiLevel;
     return path;
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
new file mode 100644
index 0000000..2c7cbe5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class TestBackportedNotPresentInAndroidJar {
+
+  @Test
+  public void testBackportedMethodsPerAPILevel() throws Exception {
+    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      if (!ToolHelper.hasAndroidJar(apiLevel)) {
+        // Only check for the android jar versions present in third_party.
+        System.out.println("Skipping check for " + apiLevel);
+        continue;
+      }
+      // Check that the backported methods for each API level are are not present in the
+      // android.jar for that level.
+      CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
+      List<DexMethod> backportedMethods =
+          BackportedMethodRewriter.generateListOfBackportedMethods(apiLevel);
+      for (DexMethod method : backportedMethods) {
+        // Two different DexItemFactories are in play, but as toSourceString is used for lookup
+        // that is not an issue.
+        ClassSubject clazz = inspector.clazz(method.holder.toSourceString());
+        MethodSubject foundInAndroidJar =
+            clazz.method(
+                method.proto.returnType.toSourceString(),
+                method.name.toSourceString(),
+                Arrays.stream(method.proto.parameters.values)
+                    .map(DexType::toSourceString)
+                    .collect(Collectors.toList()));
+        assertThat(
+            foundInAndroidJar + " present in " + apiLevel, foundInAndroidJar, not(isPresent()));
+      }
+    }
+  }
+}