Backport ExecutorService#close

Bug: b/369520931
Change-Id: I947bc5260ffd75a41b29c3fa3fcae55419a05322
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 6750b52..5ac8f30 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
@@ -1800,6 +1800,18 @@
               field,
               // Template code calls the method again.
               BackportedMethods::AndroidOsBuildVersionMethods_getSdkIntFull));
+
+      // void java.util.concurrent.ExecutorService.close()
+      type = factory.createType("Ljava/util/concurrent/ExecutorService;");
+      name = factory.createString("close");
+      DexProto proto = factory.createProto(factory.voidType);
+      DexMethod method = factory.createMethod(type, proto, name);
+      addProvider(
+          new StatifyingMethodGenerator(
+              method,
+              BackportedMethods::ExecutorServiceMethods_closeExecutorService,
+              "closeExecutorService",
+              type));
     }
 
     private void initializeAndroidUMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 494c6c9..a1078a8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -68,6 +68,7 @@
     factory.createSynthesizedType("Ljava/lang/IllegalArgumentException;");
     factory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;");
     factory.createSynthesizedType("Ljava/lang/Integer;");
+    factory.createSynthesizedType("Ljava/lang/InterruptedException;");
     factory.createSynthesizedType("Ljava/lang/Iterable;");
     factory.createSynthesizedType("Ljava/lang/Long;");
     factory.createSynthesizedType("Ljava/lang/Math;");
@@ -78,6 +79,7 @@
     factory.createSynthesizedType("Ljava/lang/Runnable;");
     factory.createSynthesizedType("Ljava/lang/RuntimeException;");
     factory.createSynthesizedType("Ljava/lang/SecurityException;");
+    factory.createSynthesizedType("Ljava/lang/Thread;");
     factory.createSynthesizedType("Ljava/lang/reflect/Constructor;");
     factory.createSynthesizedType("Ljava/lang/reflect/InvocationTargetException;");
     factory.createSynthesizedType("Ljava/lang/reflect/Method;");
@@ -103,6 +105,8 @@
     factory.createSynthesizedType("Ljava/util/OptionalInt;");
     factory.createSynthesizedType("Ljava/util/OptionalLong;");
     factory.createSynthesizedType("Ljava/util/Set;");
+    factory.createSynthesizedType("Ljava/util/concurrent/ExecutorService;");
+    factory.createSynthesizedType("Ljava/util/concurrent/TimeUnit;");
     factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReference;");
     factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceArray;");
     factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;");
@@ -2408,6 +2412,176 @@
         ImmutableList.of());
   }
 
+  public static CfCode ExecutorServiceMethods_closeExecutorService(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    CfLabel label13 = new CfLabel();
+    CfLabel label14 = new CfLabel();
+    CfLabel label15 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                185,
+                factory.createMethod(
+                    factory.createType("Ljava/util/concurrent/ExecutorService;"),
+                    factory.createProto(factory.booleanType),
+                    factory.createString("isTerminated")),
+                true),
+            new CfStore(ValueType.INT, 1),
+            label1,
+            new CfLoad(ValueType.INT, 1),
+            new CfIf(IfType.NE, ValueType.INT, label14),
+            label2,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                185,
+                factory.createMethod(
+                    factory.createType("Ljava/util/concurrent/ExecutorService;"),
+                    factory.createProto(factory.voidType),
+                    factory.createString("shutdown")),
+                true),
+            label3,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 2),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/util/concurrent/ExecutorService;")),
+                      FrameType.intType(),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.INT, 1),
+            new CfIf(IfType.NE, ValueType.INT, label12),
+            label5,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfConstNumber(1, ValueType.LONG),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/util/concurrent/TimeUnit;"),
+                    factory.createType("Ljava/util/concurrent/TimeUnit;"),
+                    factory.createString("DAYS"))),
+            new CfInvoke(
+                185,
+                factory.createMethod(
+                    factory.createType("Ljava/util/concurrent/ExecutorService;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.longType,
+                        factory.createType("Ljava/util/concurrent/TimeUnit;")),
+                    factory.createString("awaitTermination")),
+                true),
+            new CfStore(ValueType.INT, 1),
+            label6,
+            new CfGoto(label4),
+            label7,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/util/concurrent/ExecutorService;")),
+                      FrameType.intType(),
+                      FrameType.intType()
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initializedNonNullReference(
+                            factory.createType("Ljava/lang/InterruptedException;"))))),
+            new CfStore(ValueType.OBJECT, 3),
+            label8,
+            new CfLoad(ValueType.INT, 2),
+            new CfIf(IfType.NE, ValueType.INT, label11),
+            label9,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                185,
+                factory.createMethod(
+                    factory.createType("Ljava/util/concurrent/ExecutorService;"),
+                    factory.createProto(factory.createType("Ljava/util/List;")),
+                    factory.createString("shutdownNow")),
+                true),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label10,
+            new CfConstNumber(1, ValueType.INT),
+            new CfStore(ValueType.INT, 2),
+            label11,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/util/concurrent/ExecutorService;")),
+                      FrameType.intType(),
+                      FrameType.intType()
+                    })),
+            new CfGoto(label4),
+            label12,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/util/concurrent/ExecutorService;")),
+                      FrameType.intType(),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.INT, 2),
+            new CfIf(IfType.EQ, ValueType.INT, label14),
+            label13,
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Thread;"),
+                    factory.createProto(factory.createType("Ljava/lang/Thread;")),
+                    factory.createString("currentThread")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Thread;"),
+                    factory.createProto(factory.voidType),
+                    factory.createString("interrupt")),
+                false),
+            label14,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Ljava/util/concurrent/ExecutorService;")),
+                      FrameType.intType()
+                    })),
+            new CfReturnVoid(),
+            label15),
+        ImmutableList.of(
+            new CfTryCatch(
+                label5,
+                label6,
+                ImmutableList.of(factory.createType("Ljava/lang/InterruptedException;")),
+                ImmutableList.of(label7))),
+        ImmutableList.of());
+  }
+
   public static CfCode FloatMethods_isFinite(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
new file mode 100644
index 0000000..c55a8e4
--- /dev/null
+++ b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
@@ -0,0 +1,45 @@
+// 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 twr;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ExecutorServiceBackportTest extends AbstractBackportTest {
+
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK21)
+        .withDexRuntimesStartingFromExcluding(Version.V4_4_4)
+        .withAllApiLevelsAlsoForCf()
+        .build();
+  }
+
+  public ExecutorServiceBackportTest(TestParameters parameters) {
+    super(parameters, ExecutorService.class, Main.class);
+    registerTarget(AndroidApiLevel.BAKLAVA, 1);
+    ignoreInvokes("isTerminated");
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ExecutorService executorService = new ForkJoinPool();
+      System.out.println(executorService.isTerminated());
+      executorService.close();
+      System.out.println(executorService.isTerminated());
+    }
+  }
+}
diff --git a/src/test/examplesJava21/twr/LookUpCloseResourceTest.java b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
index 98f088d..d30e117 100644
--- a/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
+++ b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -42,6 +43,13 @@
 @RunWith(Parameterized.class)
 public class LookUpCloseResourceTest extends TestBase {
 
+  private static final Set<String> CANNOT_FIX =
+      ImmutableSet.of(
+          "java.net.URLClassLoader",
+          "android.net.wifi.p2p.WifiP2pManager$Channel",
+          "android.content.res.AssetFileDescriptor$AutoCloseInputStream");
+  private static final Set<String> TOO_OLD_TO_FIX =
+      ImmutableSet.of("java.nio.channels.FileLock", "android.database.sqlite.SQLiteClosable");
   private static final boolean DEBUG_PRINT = false;
   private static int MAX_PROCESSED_ANDROID_API_LEVEL = 36;
 
@@ -134,14 +142,15 @@
     Assert.assertEquals(5, closeBackports.size());
 
     if (DEBUG_PRINT) {
-      print(closeBackports, classIntroducedBeforeClose, toSuper);
+      print(closeBackports, classIntroducedBeforeClose, toSuper, appViewForMax);
     }
   }
 
   private void print(
       List<DexMethod> closeBackports,
       Map<DexType, AndroidApiLevel> classIntroducedBeforeClose,
-      Map<DexType, DexType> toSuper) {
+      Map<DexType, DexType> toSuper,
+      AppView<?> appView) {
     Map<DexType, List<DexType>> toSub = new IdentityHashMap<>();
     toSuper.forEach(
         (sup, sub) -> {
@@ -150,18 +159,26 @@
     System.out.println("Classes introduced in android.jar before their close() method override :");
     classIntroducedBeforeClose.forEach(
         (type, api) -> {
+          System.out.print(api + " ");
+          System.out.print(appView.definitionFor(type).isFinal() ? "f " : "nf ");
           System.out.print(type + " ");
+          if (closeBackports.stream().anyMatch(m -> m.getHolderType() == type)) {
+            System.out.print("-- backport");
+          }
+          if (CANNOT_FIX.contains(type.toString())) {
+            System.out.print("-- cannotfix");
+          }
+          if (TOO_OLD_TO_FIX.contains(type.toString())) {
+            System.out.print("-- tooOldToFix");
+          }
+          System.out.println();
           if (toSub.containsKey(type)) {
             System.out.print("[");
             for (DexType sub : toSub.get(type)) {
               System.out.print(sub + ", ");
             }
-            System.out.print("] ");
+            System.out.println("] ");
           }
-          if (closeBackports.stream().anyMatch(m -> m.getHolderType() == type)) {
-            System.out.print("-- backport");
-          }
-          System.out.println();
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
new file mode 100644
index 0000000..3d5a297
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/ExecutorServiceMethods.java
@@ -0,0 +1,28 @@
+package com.android.tools.r8.ir.desugar.backports;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class ExecutorServiceMethods {
+
+  public static void closeExecutorService(ExecutorService executorService) {
+    boolean terminated = executorService.isTerminated();
+    if (!terminated) {
+      executorService.shutdown();
+      boolean interrupted = false;
+      while (!terminated) {
+        try {
+          terminated = executorService.awaitTermination(1L, TimeUnit.DAYS);
+        } catch (InterruptedException e) {
+          if (!interrupted) {
+            executorService.shutdownNow();
+            interrupted = true;
+          }
+        }
+      }
+      if (interrupted) {
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 9b3ae81..5d9103c 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -51,6 +51,7 @@
           CollectionMethods.class,
           CollectionsMethods.class,
           DoubleMethods.class,
+          ExecutorServiceMethods.class,
           FloatMethods.class,
           IntegerMethods.class,
           LongMethods.class,