AutoCloseable Retargeter
Bug: b/369520931
Change-Id: Ie6c56b338d9bfe51678f09c57e4f5cbba7b83859
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index 0728314..2b49823 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -31,9 +31,12 @@
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ThrowExceptionCode;
import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryTypeRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer;
import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.records.RecordTagSynthesizer;
import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
@@ -74,6 +77,7 @@
for (SyntheticKind kind : naming.kinds()) {
assert !kind.isGlobal()
|| !kind.isMayOverridesNonProgramType()
+ || kind.equals(naming.AUTOCLOSEABLE_TAG)
|| kind.equals(naming.RECORD_TAG)
|| kind.equals(naming.API_MODEL_STUB)
|| kind.equals(naming.METHOD_HANDLES_LOOKUP)
@@ -180,6 +184,22 @@
RecordTagSynthesizer.ensureRecordClassHelper(
appView, synthesizingContext, RecordDesugaringEventConsumer.empty(), null, null);
+ AutoCloseableRetargeter.ensureAutoCloseableInterfaceTag(
+ synthesizingContext,
+ new AutoCloseableRetargeterEventConsumer() {
+ @Override
+ public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {}
+
+ @Override
+ public void acceptAutoCloseableTagContext(
+ DexProgramClass autoCloseableTag, ProgramMethod context) {}
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(
+ ProgramMethod method, ProgramMethod context) {}
+ },
+ appView);
+
VarHandleDesugaringEventConsumer varHandleEventConsumer =
VarHandleDesugaringEventConsumer.empty();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d02a4cd..27f31fa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -73,6 +73,7 @@
public static final String throwableDescriptorString = "Ljava/lang/Throwable;";
public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
+ public static final String autoCloseableTagString = "Lcom/android/tools/r8/AutoCloseableTag;";
public static final String recordDescriptorString = "Ljava/lang/Record;";
public static final String desugarVarHandleDescriptorString =
"Lcom/android/tools/r8/DesugarVarHandle;";
@@ -260,6 +261,7 @@
public final DexString objectDescriptor = createString("Ljava/lang/Object;");
public final DexString recordDescriptor = createString(recordDescriptorString);
public final DexString recordTagDescriptor = createString(recordTagDescriptorString);
+ public final DexString autoCloseableTagDescriptor = createString(autoCloseableTagString);
public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
public final DexString classDescriptor = createString("Ljava/lang/Class;");
public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -441,6 +443,7 @@
public final DexType objectType = createStaticallyKnownType(objectDescriptor);
public final DexType recordType = createStaticallyKnownType(recordDescriptor);
public final DexType recordTagType = createStaticallyKnownType(recordTagDescriptor);
+ public final DexType autoCloseableTagType = createStaticallyKnownType(autoCloseableTagDescriptor);
public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
public final DexType enumType = createStaticallyKnownType(enumDescriptor);
@@ -665,6 +668,7 @@
createStaticallyKnownType(androidContentContentProviderClientDescriptorString);
public final DexType androidDrmDrmManagerClientType =
createStaticallyKnownType(androidDrmDrmManagerClientDescriptorString);
+ public final DexType androidMediaMediaDrm = createStaticallyKnownType("Landroid/media/MediaDrm;");
public final DexType androidMediaMediaDrmType =
createStaticallyKnownType(androidMediaMediaDrmDescriptorString);
public final DexType androidMediaMediaMetadataRetrieverType =
@@ -894,6 +898,11 @@
createStaticallyKnownType(desugarMethodHandlesLookupDescriptorString);
public final DexType mockitoType = createStaticallyKnownType("Lorg/mockito/Mockito;");
+ public final DexType javaUtilConcurrentExecutorServiceType =
+ createStaticallyKnownType("Ljava/util/concurrent/ExecutorService;");
+ public final DexType javaUtilConcurrentForkJoinPoolType =
+ createStaticallyKnownType("Ljava/util/concurrent/ForkJoinPool;");
+
public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
public final IteratorMethods iteratorMethods = new IteratorMethods();
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 ee7aa09..04fb94e 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
@@ -926,21 +926,24 @@
initializeMathExactApis(factory, factory.mathType);
- // android.content.res.ContentProviderClient
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // android.content.res.ContentProviderClient
- // void android.content.ContentProviderClient.close()
- addProvider(
- new InvokeRewriter(
- factory.androidContentContentProviderClientMembers.close,
- ContentProviderClientMethodRewrites.rewriteClose()));
+ // void android.content.ContentProviderClient.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidContentContentProviderClientMembers.close,
+ ContentProviderClientMethodRewrites.rewriteClose()));
- // android.drm.DrmManagerClient
+ // android.drm.DrmManagerClient
- // void android.drm.DrmManagerClient.close()
- addProvider(
- new InvokeRewriter(
- factory.androidDrmDrmManagerClientMembers.close,
- DrmManagerClientMethodRewrites.rewriteClose()));
+ // void android.drm.DrmManagerClient.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidDrmDrmManagerClientMembers.close,
+ DrmManagerClientMethodRewrites.rewriteClose()));
+ }
}
/**
@@ -1197,11 +1200,14 @@
"stripTrailingZeros",
bigDecimal));
- // void android.drm.DrmManagerClient.close()
- addProvider(
- new InvokeRewriter(
- factory.androidMediaMetadataRetrieverMembers.close,
- MediaMetadataRetrieverMethodRewrites.rewriteClose()));
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // void android.media.MetadataRetriever.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidMediaMetadataRetrieverMembers.close,
+ MediaMetadataRetrieverMethodRewrites.rewriteClose()));
+ }
}
private void initializeAndroidRObjectsMethodProviderWithSupplier(DexItemFactory factory) {
@@ -1448,13 +1454,16 @@
new InvokeRewriter(
factory.androidUtilSparseArrayMembers.set, SparseArrayMethodRewrites.rewriteSet()));
- // android.content.res.TypedArray
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // android.content.res.TypedArray
- // void android.content.res.TypedArray.close()
- addProvider(
- new InvokeRewriter(
- factory.androidContentResTypedArrayMembers.close,
- TypedArrayMethodRewrites.rewriteClose()));
+ // void android.content.res.TypedArray.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidContentResTypedArrayMembers.close,
+ TypedArrayMethodRewrites.rewriteClose()));
+ }
}
private void initializeAndroidSv2MethodProviders(DexItemFactory factory) {
@@ -1874,17 +1883,20 @@
(Integer) versionCodeFull[1])));
}
- // void java.util.concurrent.ExecutorService.close()
- type = factory.createType("Ljava/util/concurrent/ExecutorService;");
- name = factory.createString("close");
- proto = factory.createProto(factory.voidType);
- method = factory.createMethod(type, proto, name);
- addProvider(
- new StatifyingMethodGenerator(
- method,
- BackportedMethods::ExecutorServiceMethods_closeExecutorService,
- "closeExecutorService",
- type));
+ // AutoCloseable desugaring is disabled on low min-api levels, we rely on backports instead.
+ if (!appView.options().shouldDesugarAutoCloseable()) {
+ // void java.util.concurrent.ExecutorService.close()
+ type = factory.createType("Ljava/util/concurrent/ExecutorService;");
+ name = factory.createString("close");
+ proto = factory.createProto(factory.voidType);
+ 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/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index ace4c1e..ef35dce 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibRewriterEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
@@ -57,6 +58,7 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.stream.StreamSupport;
/**
* Class that gets notified for structural changes made as a result of desugaring (e.g., the
@@ -77,7 +79,8 @@
ApiInvokeOutlinerDesugaringEventConsumer,
VarHandleDesugaringEventConsumer,
DesugaredLibRewriterEventConsumer,
- TypeSwitchDesugaringEventConsumer {
+ TypeSwitchDesugaringEventConsumer,
+ AutoCloseableRetargeterEventConsumer {
public static CfInstructionDesugaringEventConsumer createForD8(
AppView<?> appView,
@@ -259,6 +262,24 @@
}
@Override
+ public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {
+ assert StreamSupport.stream(autoCloseableTag.programMethods().spliterator(), false)
+ .allMatch(m -> m.getDefinition().isAbstract());
+ // Intentionally empty.
+ }
+
+ @Override
+ public void acceptAutoCloseableTagContext(
+ DexProgramClass autoCloseableTag, ProgramMethod context) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod context) {
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ }
+
+ @Override
public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
clazz
.programMethods()
@@ -558,6 +579,22 @@
}
@Override
+ public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {
+ // Intentionally empty. The class will be hit by tracing if required.
+ }
+
+ @Override
+ public void acceptAutoCloseableTagContext(
+ DexProgramClass autoCloseableTag, ProgramMethod context) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod context) {
+ // Intentionally empty. The method will be hit by tracing if required.
+ }
+
+ @Override
public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
// Intentionally empty. The class will be hit by tracing if required.
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 4442dfe..604920f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryLibRewriter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
@@ -120,6 +121,9 @@
if (desugaredLibraryRetargeter != null) {
desugarings.add(desugaredLibraryRetargeter);
}
+ if (appView.options().shouldDesugarAutoCloseable()) {
+ desugarings.add(new AutoCloseableRetargeter(appView));
+ }
disableDesugarer = DesugaredLibraryDisableDesugarer.create(appView);
if (disableDesugarer != null) {
desugarings.add(disableDesugarer);
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 24224c7..02eda10 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
@@ -106,6 +106,7 @@
factory.createSynthesizedType("Ljava/util/OptionalLong;");
factory.createSynthesizedType("Ljava/util/Set;");
factory.createSynthesizedType("Ljava/util/concurrent/ExecutorService;");
+ factory.createSynthesizedType("Ljava/util/concurrent/ForkJoinPool;");
factory.createSynthesizedType("Ljava/util/concurrent/TimeUnit;");
factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReference;");
factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceArray;");
@@ -2430,12 +2431,42 @@
CfLabel label13 = new CfLabel();
CfLabel label14 = new CfLabel();
CfLabel label15 = new CfLabel();
+ CfLabel label16 = new CfLabel();
+ CfLabel label17 = new CfLabel();
+ CfLabel label18 = new CfLabel();
return new CfCode(
method.holder,
4,
4,
ImmutableList.of(
label0,
+ new CfStaticFieldRead(
+ factory.createField(
+ factory.createType("Landroid/os/Build$VERSION;"),
+ factory.intType,
+ factory.createString("SDK_INT"))),
+ new CfConstNumber(23, ValueType.INT),
+ new CfIfCmp(IfType.LE, ValueType.INT, label3),
+ label1,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 184,
+ factory.createMethod(
+ factory.createType("Ljava/util/concurrent/ForkJoinPool;"),
+ factory.createProto(factory.createType("Ljava/util/concurrent/ForkJoinPool;")),
+ factory.createString("commonPool")),
+ false),
+ new CfIfCmp(IfType.NE, ValueType.OBJECT, label3),
+ label2,
+ new CfReturnVoid(),
+ label3,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(
+ new int[] {0},
+ new FrameType[] {
+ FrameType.initializedNonNullReference(
+ factory.createType("Ljava/util/concurrent/ExecutorService;"))
+ })),
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
185,
@@ -2445,10 +2476,10 @@
factory.createString("isTerminated")),
true),
new CfStore(ValueType.INT, 1),
- label1,
+ label4,
new CfLoad(ValueType.INT, 1),
- new CfIf(IfType.NE, ValueType.INT, label14),
- label2,
+ new CfIf(IfType.NE, ValueType.INT, label17),
+ label5,
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
185,
@@ -2457,10 +2488,10 @@
factory.createProto(factory.voidType),
factory.createString("shutdown")),
true),
- label3,
+ label6,
new CfConstNumber(0, ValueType.INT),
new CfStore(ValueType.INT, 2),
- label4,
+ label7,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2471,8 +2502,8 @@
FrameType.intType()
})),
new CfLoad(ValueType.INT, 1),
- new CfIf(IfType.NE, ValueType.INT, label12),
- label5,
+ new CfIf(IfType.NE, ValueType.INT, label15),
+ label8,
new CfLoad(ValueType.OBJECT, 0),
new CfConstNumber(1, ValueType.LONG),
new CfStaticFieldRead(
@@ -2491,9 +2522,9 @@
factory.createString("awaitTermination")),
true),
new CfStore(ValueType.INT, 1),
- label6,
- new CfGoto(label4),
- label7,
+ label9,
+ new CfGoto(label7),
+ label10,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2508,10 +2539,10 @@
FrameType.initializedNonNullReference(
factory.createType("Ljava/lang/InterruptedException;"))))),
new CfStore(ValueType.OBJECT, 3),
- label8,
+ label11,
new CfLoad(ValueType.INT, 2),
- new CfIf(IfType.NE, ValueType.INT, label11),
- label9,
+ new CfIf(IfType.NE, ValueType.INT, label14),
+ label12,
new CfLoad(ValueType.OBJECT, 0),
new CfInvoke(
185,
@@ -2521,10 +2552,10 @@
factory.createString("shutdownNow")),
true),
new CfStackInstruction(CfStackInstruction.Opcode.Pop),
- label10,
+ label13,
new CfConstNumber(1, ValueType.INT),
new CfStore(ValueType.INT, 2),
- label11,
+ label14,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2534,8 +2565,8 @@
FrameType.intType(),
FrameType.intType()
})),
- new CfGoto(label4),
- label12,
+ new CfGoto(label7),
+ label15,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -2546,8 +2577,8 @@
FrameType.intType()
})),
new CfLoad(ValueType.INT, 2),
- new CfIf(IfType.EQ, ValueType.INT, label14),
- label13,
+ new CfIf(IfType.EQ, ValueType.INT, label17),
+ label16,
new CfInvoke(
184,
factory.createMethod(
@@ -2562,7 +2593,7 @@
factory.createProto(factory.voidType),
factory.createString("interrupt")),
false),
- label14,
+ label17,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1},
@@ -2572,13 +2603,13 @@
FrameType.intType()
})),
new CfReturnVoid(),
- label15),
+ label18),
ImmutableList.of(
new CfTryCatch(
- label5,
- label6,
+ label8,
+ label9,
ImmutableList.of(factory.createType("Ljava/lang/InterruptedException;")),
- ImmutableList.of(label7))),
+ ImmutableList.of(label10))),
ImmutableList.of());
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
new file mode 100644
index 0000000..8756724
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
@@ -0,0 +1,211 @@
+// Copyright (c) 2025, 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.retargeter;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.createCloseMethod;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+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.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.function.BiFunction;
+import java.util.function.IntConsumer;
+import org.objectweb.asm.Opcodes;
+
+public class AutoCloseableRetargeter implements CfInstructionDesugaring {
+
+ private final AppView<?> appView;
+ private final AutoCloseableRetargeterHelper data;
+
+ public AutoCloseableRetargeter(AppView<?> appView) {
+ this.appView = appView;
+ this.data =
+ new AutoCloseableRetargeterHelper(
+ appView.options().getMinApiLevel(), appView.dexItemFactory());
+ }
+
+ public DexMethod synthesizeDispatcher(
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ AutoCloseableRetargeterEventConsumer eventConsumer) {
+ DexItemFactory factory = appView.dexItemFactory();
+ ensureAutoCloseableInterfaceTag(ImmutableList.of(context), eventConsumer, appView);
+ LinkedHashMap<DexType, DexMethod> dispatchCases =
+ data.synthesizeDispatchCases(appView, context, eventConsumer, methodProcessingContext);
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.AUTOCLOSEABLE_DISPATCHER,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setProto(factory.createProto(factory.voidType, factory.objectType))
+ .setCode(
+ methodSig ->
+ new EmulateDispatchSyntheticCfCodeProvider(
+ methodSig.getHolderType(),
+ createCloseMethod(factory, factory.autoCloseableType),
+ createCloseMethod(factory, factory.autoCloseableTagType),
+ dispatchCases,
+ EmulateDispatchType.AUTO_CLOSEABLE,
+ appView)
+ .generateCfCode()));
+ eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+ return method.getReference();
+ }
+
+ public static DexType ensureAutoCloseableInterfaceTag(
+ Collection<? extends ProgramDefinition> contexts,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ AppView<?> appView) {
+ return appView
+ .getSyntheticItems()
+ .ensureGlobalClass(
+ () -> new MissingGlobalSyntheticsConsumerDiagnostic("Autocloseable desugaring"),
+ kinds -> kinds.AUTOCLOSEABLE_TAG,
+ appView.dexItemFactory().autoCloseableTagType,
+ contexts,
+ appView,
+ builder ->
+ builder
+ .setInterface()
+ .setVirtualMethods(ImmutableList.of(synthesizeItfClose(appView))),
+ eventConsumer::acceptAutoCloseableTagProgramClass,
+ clazz -> {
+ for (ProgramDefinition context : contexts) {
+ if (context.isProgramMethod()) {
+ eventConsumer.acceptAutoCloseableTagContext(clazz, context.asProgramMethod());
+ }
+ }
+ })
+ .getType();
+ }
+
+ private static DexEncodedMethod synthesizeItfClose(AppView<?> appView) {
+ MethodAccessFlags methodAccessFlags =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT, false);
+ return DexEncodedMethod.syntheticBuilder()
+ .setMethod(
+ createCloseMethod(
+ appView.dexItemFactory(), appView.dexItemFactory().autoCloseableTagType))
+ .setAccessFlags(methodAccessFlags)
+ // Will be traced by the enqueuer.
+ .disableAndroidApiLevelCheck()
+ .build();
+ }
+
+ @Override
+ public void acceptRelevantAsmOpcodes(IntConsumer consumer) {
+ consumer.accept(Opcodes.INVOKEVIRTUAL);
+ consumer.accept(Opcodes.INVOKESPECIAL);
+ consumer.accept(Opcodes.INVOKEINTERFACE);
+ }
+
+ @Override
+ public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
+ if (instruction.isInvoke() && !instruction.isInvokeStatic()) {
+ return computeInvokeDescription(instruction, context);
+ }
+ return DesugarDescription.nothing();
+ }
+
+ private DesugarDescription computeInvokeDescription(
+ CfInstruction instruction, ProgramMethod context) {
+ if (appView
+ .getSyntheticItems()
+ .isSyntheticOfKind(context.getHolderType(), kinds -> kinds.AUTOCLOSEABLE_DISPATCHER)) {
+ return DesugarDescription.nothing();
+ }
+ CfInvoke cfInvoke = instruction.asInvoke();
+ DexMethod invokedMethod = cfInvoke.getMethod();
+ if (!data.hasCloseMethodName(invokedMethod) || invokedMethod.getArity() > 0) {
+ return DesugarDescription.nothing();
+ }
+ AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+ MethodResolutionResult resolutionResult =
+ appInfo.resolveMethodLegacy(invokedMethod, cfInvoke.isInterface());
+ if (!resolutionResult.isSingleResolution()) {
+ return DesugarDescription.nothing();
+ }
+ assert resolutionResult.getSingleTarget() != null;
+ DexMethod reference = resolutionResult.getSingleTarget().getReference();
+ if (data.shouldEmulateMethod(reference)) {
+ return computeNewTarget(reference, cfInvoke.isInvokeSuper(context.getHolderType()), context);
+ }
+ return DesugarDescription.nothing();
+ }
+
+ private DesugarDescription computeNewTarget(
+ DexMethod singleTarget, boolean superInvoke, ProgramMethod context) {
+ if (superInvoke) {
+ DexClassAndMethod dexClassAndMethod =
+ appView
+ .appInfoForDesugaring()
+ .lookupSuperTarget(singleTarget, context, appView, appView.appInfoForDesugaring());
+ if (dexClassAndMethod != null && dexClassAndMethod.isLibraryMethod()) {
+ DexType holderType = dexClassAndMethod.getHolderType();
+ if (data.superTargetsToRewrite().contains(holderType)) {
+ return createWithTarget(
+ (eventConsumer, methodProcessingContext) ->
+ data.synthesizeDispatchCase(
+ appView, holderType, context, eventConsumer, methodProcessingContext));
+ }
+ }
+ return DesugarDescription.nothing();
+ }
+ return createWithTarget(
+ (eventConsumer, methodProcessingContext) ->
+ synthesizeDispatcher(context, methodProcessingContext, eventConsumer));
+ }
+
+ private DesugarDescription createWithTarget(
+ BiFunction<CfInstructionDesugaringEventConsumer, MethodProcessingContext, DexMethod>
+ methodProvider) {
+ return DesugarDescription.builder()
+ .setDesugarRewrite(
+ (position,
+ freshLocalProvider,
+ localStackAllocator,
+ desugaringInfo,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ desugarings,
+ dexItemFactory) -> {
+ DexMethod newInvokeTarget =
+ methodProvider.apply(eventConsumer, methodProcessingContext);
+ assert appView.definitionFor(newInvokeTarget.getHolderType()) != null;
+ assert !appView.definitionFor(newInvokeTarget.getHolderType()).isInterface();
+ return Collections.singletonList(
+ new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, false));
+ })
+ .build();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
new file mode 100644
index 0000000..0b591e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2025, 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.retargeter;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface AutoCloseableRetargeterEventConsumer {
+
+ void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag);
+
+ void acceptAutoCloseableTagContext(DexProgramClass autoCloseableTag, ProgramMethod context);
+
+ void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
new file mode 100644
index 0000000..dad1e28
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2025, 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.retargeter;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableSet;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class AutoCloseableRetargeterHelper {
+
+ private final AndroidApiLevel minApiLevel;
+ private final DexItemFactory factory;
+ private final DexString close;
+ private final Set<DexMethod> methodsToEmulate;
+
+ public AutoCloseableRetargeterHelper(AndroidApiLevel minApiLevel, DexItemFactory factory) {
+ this.minApiLevel = minApiLevel;
+ this.factory = factory;
+ this.close = factory.createString("close");
+ this.methodsToEmulate = methodsToEmulate();
+ }
+
+ static DexMethod createCloseMethod(DexItemFactory factory, DexType holderType) {
+ return factory.createMethod(holderType, factory.createProto(factory.voidType), "close");
+ }
+
+ public boolean hasCloseMethodName(DexMethod method) {
+ return method.getName().isIdenticalTo(close);
+ }
+
+ public boolean shouldEmulateMethod(DexMethod method) {
+ return methodsToEmulate.contains(method);
+ }
+
+ // This includes all library types which implements AutoCloseable#close() and their subtypes.
+ // We exclude android.media.MediaDrm which is final and rewritten by the backportedMethodRewriter.
+ private Set<DexMethod> methodsToEmulate() {
+ ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+ forEachAutoCloseableSubimplementation(
+ type -> {
+ if (!type.isIdenticalTo(factory.androidMediaMediaDrmType)) {
+ builder.add(createCloseMethod(factory, type));
+ }
+ });
+ builder.add(createCloseMethod(factory, factory.autoCloseableType));
+ return builder.build();
+ }
+
+ // This includes all library types which implements directly AutoCloseable#close() including
+ // android.media.MediaDrm.
+ private void forEachAutoCloseableSubimplementation(Consumer<DexType> consumer) {
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.V)) {
+ consumer.accept(factory.javaUtilConcurrentExecutorServiceType);
+ consumer.accept(factory.javaUtilConcurrentForkJoinPoolType);
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.R)) {
+ consumer.accept(factory.androidContentResTypedArrayType);
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.P)) {
+ consumer.accept(factory.androidMediaMediaMetadataRetrieverType);
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.O_MR1)) {
+ consumer.accept(factory.androidMediaMediaDrmType);
+ }
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.M)) {
+ consumer.accept(factory.androidDrmDrmManagerClientType);
+ consumer.accept(factory.androidContentContentProviderClientType);
+ }
+ }
+
+ // This includes all library types which implements directly AutoCloseable#close() including
+ // android.media.MediaDrm, however, android.media.MediaDrm is final and rewritten if called
+ // directly by the backported method rewriter.
+ public LinkedHashMap<DexType, DexMethod> synthesizeDispatchCases(
+ AppView<?> appView,
+ ProgramMethod context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext) {
+ LinkedHashMap<DexType, DexMethod> map = new LinkedHashMap<>();
+ forEachAutoCloseableSubimplementation(
+ type -> {
+ // ForkJoinPool has an optimized version of ExecutorService.close. ForkJoinPool is not
+ // present in 19 (added in 21) so R8 cannot use instanceof ForkJoinPool in the emulated
+ // dispatch. We rely on ForkJoinPool implementing ExecutorService and use that path.
+ if (type.isNotIdenticalTo(factory.javaUtilConcurrentForkJoinPoolType)) {
+ map.put(
+ type,
+ synthesizeDispatchCase(
+ appView, type, context, eventConsumer, methodProcessingContext));
+ }
+ });
+ return map;
+ }
+
+ public Set<DexType> superTargetsToRewrite() {
+ ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+ forEachAutoCloseableSubimplementation(builder::add);
+ return builder.build();
+ }
+
+ public DexMethod synthesizeDispatchCase(
+ AppView<?> appView,
+ DexType type,
+ ProgramMethod context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext) {
+ assert superTargetsToRewrite().contains(type);
+ if (type.isIdenticalTo(factory.javaUtilConcurrentExecutorServiceType)
+ || type.isIdenticalTo(factory.javaUtilConcurrentForkJoinPoolType)) {
+ // For ForkJoinPool.close R8 uses the less efficient ExecutorService.close.
+ // ExecutorService.close does not however use unreachable apis and ExecutorService is present
+ // at Android api 19.
+ return synthesizeExecutorServiceDispatchCase(
+ appView, context, eventConsumer, methodProcessingContext);
+ }
+ if (type.isIdenticalTo(factory.androidContentResTypedArrayType)) {
+ return factory.createMethod(type, factory.createProto(factory.voidType), "recycle");
+ }
+ if (type.isIdenticalTo(factory.androidContentContentProviderClientType)) {
+ return factory.createMethod(type, factory.createProto(factory.booleanType), "release");
+ }
+ assert ImmutableSet.of(
+ factory.androidMediaMediaMetadataRetrieverType,
+ factory.androidMediaMediaDrmType,
+ factory.androidDrmDrmManagerClientType)
+ .contains(type);
+ return factory.createMethod(type, factory.createProto(factory.voidType), "release");
+ }
+
+ private DexMethod synthesizeExecutorServiceDispatchCase(
+ AppView<?> appView,
+ ProgramMethod context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext) {
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.AUTOCLOSEABLE_DISPATCHER,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setProto(
+ factory.createProto(
+ factory.voidType, factory.javaUtilConcurrentExecutorServiceType))
+ .setCode(
+ methodSig ->
+ BackportedMethods.ExecutorServiceMethods_closeExecutorService(
+ factory, methodSig)));
+ eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+ return method.getReference();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index 5777971..3413d51 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.synthesis.SyntheticClassBuilder;
import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
@@ -284,7 +285,12 @@
DexMethod itfMethod = emulatedInterfaceDispatchMethod(itfClass, descriptor);
assert descriptor.getDispatchCases().isEmpty();
return new EmulateDispatchSyntheticCfCodeProvider(
- methodSig.getHolderType(), forwardingMethod, itfMethod, new LinkedHashMap<>(), appView)
+ methodSig.getHolderType(),
+ forwardingMethod,
+ itfMethod,
+ new LinkedHashMap<>(),
+ EmulateDispatchType.ALL_STATIC,
+ appView)
.generateCfCode();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index 20086db..968ecad 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -105,6 +106,7 @@
companionMethod,
itfMethod,
extraDispatchCases,
+ EmulateDispatchType.ALL_STATIC,
appView)
.generateCfCode());
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index 9caa410..8df74a1 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.ir.synthetic;
+import static com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType.ALL_STATIC;
+import static com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider.EmulateDispatchType.AUTO_CLOSEABLE;
+
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfIf;
@@ -29,8 +32,14 @@
public class EmulateDispatchSyntheticCfCodeProvider extends SyntheticCfCodeProvider {
+ public enum EmulateDispatchType {
+ ALL_STATIC,
+ AUTO_CLOSEABLE
+ }
+
private final DexMethod forwardingMethod;
private final DexMethod interfaceMethod;
+ private final EmulateDispatchType dispatchType;
private final LinkedHashMap<DexType, DexMethod> extraDispatchCases;
public EmulateDispatchSyntheticCfCodeProvider(
@@ -38,16 +47,21 @@
DexMethod forwardingMethod,
DexMethod interfaceMethod,
LinkedHashMap<DexType, DexMethod> extraDispatchCases,
+ EmulateDispatchType dispatchType,
AppView<?> appView) {
super(appView, holder);
this.forwardingMethod = forwardingMethod;
this.interfaceMethod = interfaceMethod;
this.extraDispatchCases = extraDispatchCases;
+ this.dispatchType = dispatchType;
}
@Override
public CfCode generateCfCode() {
- DexType receiverType = forwardingMethod.getParameter(0);
+ DexType receiverType =
+ dispatchType == AUTO_CLOSEABLE
+ ? forwardingMethod.getHolderType()
+ : forwardingMethod.getParameter(0);
List<CfInstruction> instructions = new ArrayList<>();
CfLabel[] labels = new CfLabel[extraDispatchCases.size() + 1];
for (int i = 0; i < labels.length; i++) {
@@ -89,8 +103,7 @@
// Call basic block.
instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
instructions.add(new CfCheckCast(dispatch.getKey()));
- loadExtraParameters(instructions);
- instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, dispatch.getValue(), false));
+ forwardCall(instructions, dispatch.getValue());
addReturn(instructions);
}
@@ -98,12 +111,28 @@
instructions.add(labels[nextLabel]);
instructions.add(frame.clone());
instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
- loadExtraParameters(instructions);
- instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, forwardingMethod, false));
+ forwardCall(instructions, forwardingMethod);
addReturn(instructions);
return standardCfCodeFromInstructions(instructions);
}
+ private void forwardCall(List<CfInstruction> instructions, DexMethod method) {
+ if (dispatchType == ALL_STATIC
+ || appView.getSyntheticItems().isSynthetic(method.getHolderType())) {
+ loadExtraParameters(instructions);
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+ return;
+ }
+ assert dispatchType == AUTO_CLOSEABLE;
+ instructions.add(new CfCheckCast(method.holder));
+ loadExtraParameters(instructions);
+ if (appView.definitionFor(method.getHolderType()).isInterface()) {
+ instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, method, true));
+ } else {
+ instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
+ }
+ }
+
private void loadExtraParameters(List<CfInstruction> instructions) {
int index = 1;
for (DexType type : interfaceMethod.proto.parameters.values) {
@@ -113,10 +142,10 @@
@SuppressWarnings("ReferenceEquality")
private void addReturn(List<CfInstruction> instructions) {
- if (interfaceMethod.proto.returnType == appView.dexItemFactory().voidType) {
+ if (interfaceMethod.getReturnType().isVoidType()) {
instructions.add(new CfReturnVoid());
} else {
- instructions.add(new CfReturn(ValueType.fromDexType(interfaceMethod.proto.returnType)));
+ instructions.add(new CfReturn(ValueType.fromDexType(interfaceMethod.getReturnType())));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
index 3af5dc4..ee53f13 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -342,6 +342,29 @@
}
@Override
+ public void acceptAutoCloseableTagProgramClass(DexProgramClass autoCloseableTag) {
+ parent.acceptAutoCloseableTagProgramClass(autoCloseableTag);
+ }
+
+ @Override
+ public void acceptAutoCloseableTagContext(
+ DexProgramClass autoCloseableTag, ProgramMethod context) {
+ additionsCollection.applyIfContextIsInProfile(
+ context,
+ additionsBuilder -> {
+ additionsBuilder.addRule(autoCloseableTag);
+ autoCloseableTag.forEachProgramMethod(additionsBuilder::addRule);
+ });
+ parent.acceptAutoCloseableTagContext(autoCloseableTag, context);
+ }
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramMethod context) {
+ additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+ parent.acceptAutoCloseableDispatchMethod(method, context);
+ }
+
+ @Override
public void acceptRecordClass(DexProgramClass recordClass) {
parent.acceptRecordClass(recordClass);
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 0030cec..1cf3883 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -26,6 +26,7 @@
private KindGenerator generator = new KindGenerator();
// Global synthetics.
+ public final SyntheticKind AUTOCLOSEABLE_TAG = generator.forGlobalClass();
public final SyntheticKind RECORD_TAG = generator.forGlobalClass();
public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass();
public final SyntheticKind METHOD_HANDLES_LOOKUP = generator.forGlobalClass();
@@ -66,6 +67,8 @@
public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy");
// Method synthetics.
+ public final SyntheticKind AUTOCLOSEABLE_DISPATCHER =
+ generator.forSingleMethodWithGlobalMerging("AutoCloseableDispatcher");
public final SyntheticKind TYPE_SWITCH_HELPER =
generator.forSingleMethodWithGlobalMerging("TypeSwitch");
public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c54080b..b8ea329 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2122,6 +2122,7 @@
System.getProperty("com.android.tools.r8.enableKeepAnnotations") != null;
public boolean reverseClassSortingForDeterminism = false;
+ public boolean enableAutoCloseableDesugaring = false;
public boolean enableNumberUnboxer = false;
public boolean printNumberUnboxed = false;
public boolean roundtripThroughLir = false;
@@ -2698,6 +2699,13 @@
&& !canUseDefaultAndStaticInterfaceMethods();
}
+ public boolean shouldDesugarAutoCloseable() {
+ return desugarState.isOn()
+ && getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+ && getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.V)
+ && testing.enableAutoCloseableDesugaring;
+ }
+
public boolean isSwitchRewritingEnabled() {
return enableSwitchRewriting && !debug;
}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java b/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java
new file mode 100644
index 0000000..a6c9881
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableDesugaringClassesPresentAtKitKat.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryTypeRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper;
+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.Timing;
+import com.google.common.collect.Sets;
+import java.nio.file.Path;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class AutoCloseableDesugaringClassesPresentAtKitKat extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ InternalOptions options = new InternalOptions();
+ Path androidJarK = ToolHelper.getAndroidJar(AndroidApiLevel.K);
+ AndroidApp app = AndroidApp.builder().addProgramFile(androidJarK).build();
+ DirectMappedDexApplication libHolder =
+ new ApplicationReader(app, options, Timing.empty()).read().toDirect();
+ AppInfo initialAppInfo =
+ AppInfo.createInitialAppInfo(libHolder, GlobalSyntheticsStrategy.forNonSynthesizing());
+ AppView<AppInfo> appView =
+ AppView.createForD8(initialAppInfo, DesugaredLibraryTypeRewriter.empty(), Timing.empty());
+
+ AutoCloseableRetargeterHelper data =
+ new AutoCloseableRetargeterHelper(AndroidApiLevel.B, appView.dexItemFactory());
+ Set<DexType> missing = Sets.newIdentityHashSet();
+ for (DexType dexType : data.superTargetsToRewrite()) {
+ if (appView.definitionFor(dexType) == null) {
+ missing.add(dexType);
+ }
+ }
+ assertEquals(1, missing.size());
+ // ForkJoinPool is missing at Android Api level 19 but that's ok since it implements
+ // ExecutorService.close in a more optimized way. We rely on ExecutorService for the
+ // emulated dispatch.
+ assertEquals(
+ options.dexItemFactory().javaUtilConcurrentForkJoinPoolType, missing.iterator().next());
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java
new file mode 100644
index 0000000..e68e264
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidTest.java
@@ -0,0 +1,160 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.ContentProviderClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClient;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaDrm;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetriever;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArray;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AutoCloseableRetargeterAndroidTest extends AbstractBackportTest {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public AutoCloseableRetargeterAndroidTest(TestParameters parameters) throws IOException {
+ super(parameters, getTestRunner(), ImmutableList.of(getTestRunner()));
+
+ // The constructor is used by the test and release has been available since API 5 and is the
+ // method close is rewritten to.
+ ignoreInvokes("<init>");
+ ignoreInvokes("release");
+
+ registerTarget(AndroidApiLevel.B, 5);
+ }
+
+ @Override
+ protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+ d8TestBuilder.addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true);
+ }
+
+ @Override
+ protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
+ super.configureProgram(builder);
+ if (builder.isJvmTestBuilder()) {
+ builder.addProgramClassFileData(getAutoCloseableAndroidClassData(parameters));
+ } else {
+ builder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Override
+ protected void configure(D8TestCompileResult builder) throws Exception {
+ if (parameters.isDexRuntime()) {
+ builder.addBootClasspathFiles(compileAutoCloseableAndroidLibraryClasses(this, parameters));
+ } else {
+ builder.addRunClasspathClassFileData(getAutoCloseableAndroidClassData(parameters));
+ }
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addStrippedOuter(getClass())
+ .apply(this::configureProgram)
+ .run(parameters.getRuntime(), getTestClassName())
+ // Fails when not desugared.
+ .assertFailureWithErrorThatMatches(containsString("close should not be called"));
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(ContentProviderClient.class),
+ DexItemFactory.androidContentContentProviderClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(DrmManagerClient.class),
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaDrm.class), DexItemFactory.androidMediaMediaDrmDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaMetadataRetriever.class),
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TypedArray.class),
+ DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .clearNest()
+ .transform();
+ }
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) {
+ contentProviderClient();
+ drmManagerClient();
+ mediaDrm();
+ mediaMetadataRetriever();
+ typedArray();
+ }
+
+ private static void contentProviderClient() {
+ ContentProviderClient contentProviderClient = new ContentProviderClient();
+ MiniAssert.assertFalse(contentProviderClient.wasClosed);
+ // Loop as regression test for b/276874854.
+ for (int i = 0; i < 2; i++) {
+ contentProviderClient.close();
+ MiniAssert.assertTrue(contentProviderClient.wasClosed);
+ }
+ }
+
+ public static void drmManagerClient() {
+ DrmManagerClient drmManagerClient = new DrmManagerClient();
+ MiniAssert.assertFalse(drmManagerClient.wasClosed);
+ drmManagerClient.close();
+ MiniAssert.assertTrue(drmManagerClient.wasClosed);
+ }
+
+ public static void mediaDrm() {
+ MediaDrm mediaDrm = new MediaDrm();
+ MiniAssert.assertFalse(mediaDrm.wasClosed);
+ mediaDrm.close();
+ MiniAssert.assertTrue(mediaDrm.wasClosed);
+ }
+
+ public static void mediaMetadataRetriever() {
+ MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
+ MiniAssert.assertFalse(mediaMetadataRetriever.wasClosed);
+ mediaMetadataRetriever.close();
+ MiniAssert.assertTrue(mediaMetadataRetriever.wasClosed);
+ }
+
+ public static void typedArray() {
+ TypedArray typedArray = new TypedArray();
+ MiniAssert.assertFalse(typedArray.wasClosed);
+ typedArray.close();
+ MiniAssert.assertTrue(typedArray.wasClosed);
+ }
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java
new file mode 100644
index 0000000..ae043fd
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2025, 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 autocloseable;
+
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AutoCloseableRetargeterExecutorServiceTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .build();
+ }
+
+ private static String EXPECTED_OUTPUT = StringUtils.lines("false", "true", "SUCCESS");
+ private static String EXPECTED_OUTPUT_24 =
+ StringUtils.lines("false", "true", "false", "false", "SUCCESS");
+
+ private String getExpectedOutput() {
+ return parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)
+ ? EXPECTED_OUTPUT
+ : EXPECTED_OUTPUT_24;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+ .setMinApi(parameters)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .compile()
+ .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+ .run(
+ parameters.getRuntime(),
+ Main.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(Main.class)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addInliningAnnotations()
+ .addOptionsModification(opt -> opt.testing.enableAutoCloseableDesugaring = true)
+ .setMinApi(parameters)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .addLibraryClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .compile()
+ .addRunClasspathClassFileData((getAutoCloseableAndroidClassData(parameters)))
+ .run(
+ parameters.getRuntime(),
+ Main.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ int api = Integer.parseInt(args[0]);
+
+ ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
+ System.out.println(pool.isTerminated());
+ pool.close();
+ System.out.println(pool.isTerminated());
+
+ if (api >= 21) {
+ ForkJoinPool forkJoinPool = new ForkJoinPool();
+ forkJoinPool.close();
+ forkJoinPool = new ForkJoinPool();
+ close(forkJoinPool);
+
+ ExecutorService executorService = new ForkJoinPool();
+ executorService.close();
+ executorService = new ForkJoinPool();
+ closeExecutorService(executorService);
+ }
+
+ if (api >= 24) {
+ ForkJoinPool commonPool = ForkJoinPool.commonPool();
+ System.out.println(commonPool.isTerminated());
+ commonPool.close();
+ // Common pool should never terminate.
+ System.out.println(commonPool.isTerminated());
+ }
+
+ System.out.println("SUCCESS");
+ }
+
+ @NeverInline
+ public static void closeExecutorService(ExecutorService ac) throws Exception {
+ ac.close();
+ }
+
+ @NeverInline
+ public static void close(AutoCloseable ac) throws Exception {
+ ac.close();
+ }
+ }
+}
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
index 3d5a297..c0382b0 100644
--- 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
@@ -1,11 +1,21 @@
package com.android.tools.r8.ir.desugar.backports;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
public class ExecutorServiceMethods {
+ // Stub out android.os.Build$VERSION as it does not exist when building R8.
+ private static class AndroidOsBuildVersionStub {
+ public static int SDK_INT;
+ }
public static void closeExecutorService(ExecutorService executorService) {
+ if (AndroidOsBuildVersionStub.SDK_INT > 23) {
+ if (executorService == ForkJoinPool.commonPool()) {
+ return;
+ }
+ }
boolean terminated = executorService.isTerminated();
if (!terminated) {
executorService.shutdown();
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 96dd2e3..40d6b6d 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
@@ -169,7 +169,13 @@
private static CfInstruction rewriteToAndroidOsBuildVersion(
DexItemFactory itemFactory, CfInstruction instruction) {
// Rewrite references to UnsafeStub to sun.misc.Unsafe.
- if (instruction.isStaticFieldGet()) {
+ if (instruction.isStaticFieldGet()
+ && instruction
+ .asFieldInstruction()
+ .getField()
+ .getHolderType()
+ .toString()
+ .contains("Stub")) {
CfStaticFieldRead fieldGet = instruction.asStaticFieldGet();
return new CfStaticFieldRead(
itemFactory.createField(
@@ -182,7 +188,10 @@
.asFrame()
.mapReferenceTypes(
type -> {
- throw new RuntimeException("Unexpected CfFrame instruction.");
+ if (type.toString().contains("Stub")) {
+ throw new RuntimeException("Unexpected CfFrame instruction.");
+ }
+ return type;
});
}
return instruction;
@@ -206,6 +215,12 @@
.map(instruction -> rewriteToUnsafe(factory, instruction))
.collect(Collectors.toList()));
}
+ if (holderName.equals("ExecutorServiceMethods") && methodName.equals("closeExecutorService")) {
+ code.setInstructions(
+ code.getInstructions().stream()
+ .map(instruction -> rewriteToAndroidOsBuildVersion(factory, instruction))
+ .collect(Collectors.toList()));
+ }
if (holderName.equals("AndroidOsBuildVersionMethods") && methodName.equals("getSdkIntFull")) {
code.setInstructions(
code.getInstructions().stream()
diff --git a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
index da2771f..25d4fab 100644
--- a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
@@ -46,6 +46,11 @@
}
@Override
+ public boolean isJvmTestBuilder() {
+ return true;
+ }
+
+ @Override
JvmTestBuilder self() {
return this;
}
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index ea40b20..294f719 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -96,6 +96,10 @@
return self;
}
+ public boolean isJvmTestBuilder() {
+ return false;
+ }
+
@Deprecated
public RR run(String mainClass)
throws CompilationFailedException, ExecutionException, IOException {
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java b/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java
new file mode 100644
index 0000000..ec7aa63
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/AutoCloseableAndroidLibraryFileData.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2025, 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;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class AutoCloseableAndroidLibraryFileData extends TestBase {
+
+ public static Path compileAutoCloseableAndroidLibraryClasses(
+ TestBase testBase, TestParameters parameters) throws Exception {
+ return testBase
+ .testForD8()
+ .addProgramClassFileData(getAutoCloseableAndroidClassData(parameters))
+ .setMinApi(parameters)
+ .compile()
+ .writeToZip();
+ }
+
+ public static ImmutableList<byte[]> getAutoCloseableAndroidClassData(TestParameters parameters)
+ throws Exception {
+ return ImmutableList.of(
+ getMediaDrm(parameters),
+ getContentProviderClient(parameters),
+ getDrmManagerClient(parameters),
+ getMediaMetadataRetriever(parameters),
+ getTypedArray(parameters),
+ getTransformedBuildVERSIONClass());
+ }
+
+ private static byte[] getContentProviderClient(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.N,
+ ContentProviderClientApiLevel24.class,
+ ContentProviderClient.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString);
+ }
+
+ private static byte[] getDrmManagerClient(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.N,
+ DrmManagerClientApiLevel24.class,
+ DrmManagerClient.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString);
+ }
+
+ private static byte[] getMediaDrm(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.P,
+ MediaDrmApiLevel28.class,
+ MediaDrm.class,
+ DexItemFactory.androidMediaMediaDrmDescriptorString);
+ }
+
+ private static byte[] getMediaMetadataRetriever(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.Q,
+ MediaMetadataRetrieverApiLevel29.class,
+ MediaMetadataRetriever.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString);
+ }
+
+ private static byte[] getTypedArray(TestParameters parameters) throws IOException {
+ return getTransformedClass(
+ parameters,
+ AndroidApiLevel.S,
+ TypedArrayAndroidApiLevel31.class,
+ TypedArray.class,
+ DexItemFactory.androidContentResTypedArrayDescriptorString);
+ }
+
+ private static byte[] getTransformedClass(
+ TestParameters parameters,
+ AndroidApiLevel minApiLevel,
+ Class<?> lowApiClass,
+ Class<?> highApiClass,
+ String descriptorString)
+ throws IOException {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(minApiLevel)) {
+ return transformer(lowApiClass).setClassDescriptor(descriptorString).clearNest().transform();
+ } else {
+ return transformer(highApiClass).setClassDescriptor(descriptorString).clearNest().transform();
+ }
+ }
+
+ private static byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+ return transformer(VERSION.class)
+ .setClassDescriptor("Landroid/os/Build$VERSION;")
+ .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+ .transform();
+ }
+
+ // Minimal android.os.Build$VERSION for runtime classpath.
+ public static class /*android.os.Build$*/ VERSION {
+
+ public static /*final*/ int SDK_INT = -1;
+ }
+
+ public static class ContentProviderClient {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public boolean release() {
+ wasClosed = true;
+ return wasClosed;
+ }
+ }
+
+ public static class ContentProviderClientApiLevel24 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public boolean release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class DrmManagerClient {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void release() {
+ wasClosed = true;
+ }
+ }
+
+ public static class DrmManagerClientApiLevel24 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class MediaDrm {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void release() {
+ wasClosed = true;
+ }
+ }
+
+ public static class MediaDrmApiLevel28 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class MediaMetadataRetriever {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void release() {
+ wasClosed = true;
+ }
+ }
+
+ public static class MediaMetadataRetrieverApiLevel29 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void release() {
+ throw new AssertionError("release should not be called");
+ }
+ }
+
+ public static class TypedArray {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ throw new AssertionError("close should not be called");
+ }
+
+ public void recycle() {
+ wasClosed = true;
+ }
+ }
+
+ public static class TypedArrayAndroidApiLevel31 implements AutoCloseable {
+
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void recycle() {
+ throw new AssertionError("recycle should not be called");
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 8eebd4a..e2a2276 100644
--- a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -189,7 +189,7 @@
ignoredInvokes.add(methodName);
}
- protected void configureProgram(TestBuilder<?, ?> builder) throws IOException {
+ protected void configureProgram(TestBuilder<?, ?> builder) throws Exception {
builder.addProgramClasses(MiniAssert.class, IgnoreInvokes.class);
if (testClass != null) {
testClass.addAsProgramClass(builder);
@@ -198,6 +198,10 @@
}
}
+ protected void configureD8Options(D8TestBuilder d8TestBuilder) throws IOException {
+ // Intentionally empty.
+ }
+
@Test
public void testJvm() throws Exception {
parameters.assumeJvmTestParameters();
@@ -233,6 +237,7 @@
testForD8()
.setMinApi(parameters)
.apply(this::configureProgram)
+ .apply(this::configureD8Options)
.setIncludeClassesChecksum(true)
.compileWithExpectedDiagnostics(this::checkDiagnostics)
.apply(this::configure)
@@ -256,6 +261,7 @@
testForD8(Backend.CF)
.setMinApi(parameters)
.apply(this::configureProgram)
+ .apply(this::configureD8Options)
.setIncludeClassesChecksum(true)
.compileWithExpectedDiagnostics(this::checkDiagnostics)
.run(parameters.getRuntime(), testClassName)