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,