Reapply "AutoCloseable Retargeter"
This reverts commit f47bed9866d57634e314baf8adecbf2a506ba81e.
Bug: b/369520931
Change-Id: I787d97ce1678bbde172ae9aa5d07bd8e052294cc
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index d60a682..0dc6918 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1274,7 +1274,11 @@
}
public static DexEncodedMethod createDesugaringForwardingMethod(
- DexClassAndMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) {
+ DexClassAndMethod target,
+ DexClass clazz,
+ DexMethod forwardMethod,
+ DexItemFactory factory,
+ boolean targetIsStatic) {
assert forwardMethod != null;
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
@@ -1285,16 +1289,23 @@
newFlags.unsetAbstract();
// Holder is companion class, or retarget method, not an interface.
boolean isInterfaceMethodReference = false;
+ ForwardMethodBuilder builder =
+ ForwardMethodBuilder.builder(factory).setNonStaticSource(newMethod);
+ if (targetIsStatic) {
+ builder.setStaticTarget(forwardMethod, isInterfaceMethodReference);
+ } else {
+ builder.setVirtualTarget(forwardMethod, isInterfaceMethodReference);
+ }
+ if (forwardMethod.getReturnType().isNotIdenticalTo(target.getReturnType())) {
+ assert target.getReturnType().isVoidType();
+ builder.setIgnoreTargetResult();
+ }
return syntheticBuilder()
.setMethod(newMethod)
.setAccessFlags(newFlags)
.setGenericSignature(MethodTypeSignature.noSignature())
.setAnnotations(DexAnnotationSet.empty())
- .setCode(
- ForwardMethodBuilder.builder(factory)
- .setNonStaticSource(newMethod)
- .setStaticTarget(forwardMethod, isInterfaceMethodReference)
- .buildCf())
+ .setCode(builder.buildCf())
.setApiLevelForDefinition(target.getDefinition().getApiLevelForDefinition())
.setApiLevelForCode(target.getDefinition().getApiLevelForCode())
.build();
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/analysis/type/InterfaceCollection.java b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
index 62462bd..ae47e34 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
@@ -5,6 +5,8 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
@@ -21,14 +23,27 @@
public class InterfaceCollection {
- @SuppressWarnings("ReferenceEquality")
public static boolean isKnownToImplement(
DexType iface, DexType implementor, InternalOptions options) {
if (options.canHaveZipFileWithMissingCloseableBug()
- && implementor == options.dexItemFactory().zipFileType
- && iface == options.dexItemFactory().closeableType) {
+ && implementor.isIdenticalTo(options.dexItemFactory().zipFileType)
+ && iface.isIdenticalTo(options.dexItemFactory().closeableType)) {
return false;
}
+ if (options.canHaveMissingImplementsAutoCloseableInterface()
+ && iface.isIdenticalTo(options.dexItemFactory().autoCloseableType)) {
+ BooleanBox booleanBox = new BooleanBox(true);
+ AutoCloseableRetargeterHelper.forEachAutoCloseableMissingSubimplementation(
+ type -> {
+ if (type.isIdenticalTo(implementor)) {
+ booleanBox.set(false);
+ }
+ },
+ options.getMinApiLevel(),
+ options.dexItemFactory(),
+ true);
+ return booleanBox.get();
+ }
return true;
}
@@ -44,10 +59,6 @@
|| isKnownToImplement(iface, implementor.getType(), options));
}
- public Builder addInterface(DexType iface, DexType implementor, InternalOptions options) {
- return addInterface(iface, isKnownToImplement(iface, implementor, options));
- }
-
public Builder addInterface(DexType type, boolean isKnown) {
interfaces.compute(
type,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
index 855b473..52386c8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -311,6 +311,13 @@
.isPossiblyFalse()) {
return false;
}
+ if (appView.options().canHaveMissingImplementsAutoCloseableInterface()
+ && instanceOfBaseType.isIdenticalTo(appView.dexItemFactory().autoCloseableType)
+ && instanceOfClass.isLibraryClass()) {
+ // Library classes may be messed up since they may implement AutoCloseable from a different
+ // api level than the api level they are introduced.
+ return false;
+ }
}
Value inValue = instanceOf.value();
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..2363fe4 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;
@@ -77,7 +78,8 @@
ApiInvokeOutlinerDesugaringEventConsumer,
VarHandleDesugaringEventConsumer,
DesugaredLibRewriterEventConsumer,
- TypeSwitchDesugaringEventConsumer {
+ TypeSwitchDesugaringEventConsumer,
+ AutoCloseableRetargeterEventConsumer {
public static CfInstructionDesugaringEventConsumer createForD8(
AppView<?> appView,
@@ -259,6 +261,11 @@
}
@Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ }
+
+ @Override
public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
clazz
.programMethods()
@@ -558,6 +565,11 @@
}
@Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition 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/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index f31a4a7..dd8e727 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPICallbackSynthesizer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.disabledesugarer.DesugaredLibraryDisableDesugarerPostProcessor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterPostProcessor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterPostProcessor;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
import com.android.tools.r8.ir.desugar.records.RecordClassDesugaring;
@@ -64,6 +65,9 @@
&& !appView.options().getLibraryDesugaringOptions().isDesugaredLibraryCompilation()) {
desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView));
}
+ if (appView.options().testing.enableAutoCloseableDesugaring) {
+ desugarings.add(new AutoCloseableRetargeterPostProcessor(appView));
+ }
if (interfaceMethodProcessorFacade != null) {
desugarings.add(interfaceMethodProcessorFacade);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 9cdb6e6..e1a3cd9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -11,10 +11,12 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.D8MethodProcessor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer.AutoCloseableRetargeterPostProcessingEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
@@ -35,7 +37,8 @@
public abstract class CfPostProcessingDesugaringEventConsumer
implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
InterfaceProcessingDesugaringEventConsumer,
- DesugaredLibraryAPICallbackSynthesizorEventConsumer {
+ DesugaredLibraryAPICallbackSynthesizorEventConsumer,
+ AutoCloseableRetargeterPostProcessingEventConsumer {
public static CfPostProcessingDesugaringEventConsumer createForD8(
AppView<?> appView,
@@ -108,6 +111,12 @@
}
@Override
+ public void acceptAutoCloseableInterfaceInjection(
+ DexProgramClass clazz, DexClass newInterface) {
+ // Intentionally empty.
+ }
+
+ @Override
public void acceptEmulatedInterfaceMarkerInterface(
DexProgramClass clazz, DexClasspathClass newInterface) {
// Intentionally empty.
@@ -169,6 +178,17 @@
public void acceptGenericApiConversionStub(DexClasspathClass dexClasspathClass) {
// Intentionally empty.
}
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ addMethodToReprocess(method);
+ }
+
+ @Override
+ public void acceptAutoCloseableForwardingMethod(
+ ProgramMethod method, ProgramDefinition context) {
+ addMethodToReprocess(method);
+ }
}
public static class R8PostProcessingDesugaringEventConsumer
@@ -218,6 +238,12 @@
}
@Override
+ public void acceptAutoCloseableInterfaceInjection(
+ DexProgramClass clazz, DexClass newInterface) {
+ additions.injectInterface(clazz, newInterface);
+ }
+
+ @Override
public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
additions.addLiveClasspathClass(clazz);
}
@@ -271,5 +297,16 @@
public void acceptGenericApiConversionStub(DexClasspathClass clazz) {
additions.addLiveClasspathClass(clazz);
}
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ additions.addLiveMethod(method);
+ }
+
+ @Override
+ public void acceptAutoCloseableForwardingMethod(
+ ProgramMethod method, ProgramDefinition context) {
+ additions.addLiveMethod(method);
+ }
}
}
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..703067b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeter.java
@@ -0,0 +1,186 @@
+// 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 static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterHelper.lookupSuperIncludingInterfaces;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+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.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.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 java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+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();
+ 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(),
+ data.createThrowUnsupportedException(
+ appView,
+ context,
+ eventConsumer,
+ methodProcessingContext::createUniqueContext)
+ .getReference(),
+ createCloseMethod(factory, factory.autoCloseableType),
+ dispatchCases,
+ EmulateDispatchType.AUTO_CLOSEABLE,
+ appView)
+ .generateCfCode()));
+ eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+ return method.getReference();
+ }
+
+ @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 superMethod =
+ lookupSuperIncludingInterfaces(appView, singleTarget, context.getContextClass());
+ if (superMethod != null && superMethod.isLibraryMethod()) {
+ DexType holderType = superMethod.getHolderType();
+ if (data.superTargetsToRewrite().contains(holderType)) {
+ return createWithTarget(
+ singleTarget,
+ (eventConsumer, methodProcessingContext) ->
+ data.synthesizeDispatchCase(
+ appView,
+ holderType,
+ context,
+ eventConsumer,
+ methodProcessingContext::createUniqueContext));
+ }
+ }
+ return DesugarDescription.nothing();
+ }
+ return createWithTarget(
+ singleTarget,
+ (eventConsumer, methodProcessingContext) ->
+ synthesizeDispatcher(context, methodProcessingContext, eventConsumer));
+ }
+
+ private DesugarDescription createWithTarget(
+ DexMethod target,
+ 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();
+ List<CfInstruction> instructions = new ArrayList<>();
+ if (appView.getSyntheticItems().isSynthetic(newInvokeTarget.getHolderType())) {
+ instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, false));
+ } else {
+ instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newInvokeTarget, false));
+ }
+ if (target.getReturnType().isVoidType()
+ && !newInvokeTarget.getReturnType().isVoidType()) {
+ instructions.add(new CfStackInstruction(Opcode.Pop));
+ }
+ return instructions;
+ })
+ .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..9a29029
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterEventConsumer.java
@@ -0,0 +1,23 @@
+// 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.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface AutoCloseableRetargeterEventConsumer {
+
+ void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context);
+
+ interface AutoCloseableRetargeterPostProcessingEventConsumer
+ extends AutoCloseableRetargeterEventConsumer {
+
+ void acceptAutoCloseableForwardingMethod(ProgramMethod method, ProgramDefinition context);
+
+ void acceptAutoCloseableInterfaceInjection(DexProgramClass clazz, DexClass newInterface);
+ }
+}
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..3597e57
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterHelper.java
@@ -0,0 +1,234 @@
+// 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.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
+import com.android.tools.r8.ir.synthetic.ThrowCfCodeProvider;
+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;
+import java.util.function.Supplier;
+
+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();
+ forEachAutoCloseableMissingSubimplementation(
+ 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.
+ public static void forEachAutoCloseableMissingSubimplementation(
+ Consumer<DexType> consumer,
+ AndroidApiLevel minApiLevel,
+ DexItemFactory factory,
+ boolean withSubtypes) {
+ if (minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.V)) {
+ consumer.accept(factory.javaUtilConcurrentExecutorServiceType);
+ consumer.accept(factory.javaUtilConcurrentForkJoinPoolType);
+ if (withSubtypes) {
+ consumer.accept(factory.createType("Ljava/util/concurrent/ScheduledExecutorService;"));
+ consumer.accept(factory.createType("Ljava/util/concurrent/AbstractExecutorService;"));
+ consumer.accept(factory.createType("Ljava/util/concurrent/ThreadPoolExecutor;"));
+ consumer.accept(factory.createType("Ljava/util/concurrent/ScheduledThreadPoolExecutor;"));
+ }
+ }
+ 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);
+ }
+ }
+
+ private void forEachAutoCloseableMissingSubimplementation(Consumer<DexType> consumer) {
+ forEachAutoCloseableMissingSubimplementation(consumer, minApiLevel, factory, false);
+ }
+
+ // 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<>();
+ forEachAutoCloseableMissingSubimplementation(
+ 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::createUniqueContext));
+ }
+ });
+ return map;
+ }
+
+ public Set<DexType> superTargetsToRewrite() {
+ ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+ forEachAutoCloseableMissingSubimplementation(builder::add);
+ return builder.build();
+ }
+
+ public DexMethod synthesizeDispatchCase(
+ AppView<?> appView,
+ DexType type,
+ ProgramDefinition context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ 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, contextSupplier);
+ }
+ 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,
+ ProgramDefinition context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.AUTOCLOSEABLE_FORWARDER,
+ contextSupplier.get(),
+ 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();
+ }
+
+ ProgramMethod createThrowUnsupportedException(
+ AppView<?> appView,
+ ProgramDefinition context,
+ AutoCloseableRetargeterEventConsumer eventConsumer,
+ Supplier<UniqueContext> contextSupplier) {
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kinds -> kinds.THROW_IAE,
+ contextSupplier.get(),
+ appView,
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setProto(factory.createProto(factory.voidType, factory.objectType))
+ .setCode(
+ methodSig ->
+ new ThrowCfCodeProvider(
+ appView,
+ methodSig.getHolderType(),
+ factory.illegalArgumentExceptionType,
+ null)
+ .generateCfCode()));
+ eventConsumer.acceptAutoCloseableDispatchMethod(method, context);
+ return method;
+ }
+
+ static DexClassAndMethod lookupSuperIncludingInterfaces(
+ AppView<?> appView, DexMethod target, DexProgramClass context) {
+ DexClassAndMethod superMethod =
+ appView
+ .appInfoForDesugaring()
+ .lookupSuperTarget(target, context, appView, appView.appInfoForDesugaring());
+ if (superMethod != null) {
+ return superMethod;
+ }
+ return appView
+ .appInfoForDesugaring()
+ .lookupMaximallySpecificMethod(context.getContextClass(), target);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.java
new file mode 100644
index 0000000..b45317b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/AutoCloseableRetargeterPostProcessor.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.lookupSuperIncludingInterfaces;
+
+import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.AutoCloseableRetargeterEventConsumer.AutoCloseableRetargeterPostProcessingEventConsumer;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+
+// The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
+// for inserting interfaces on library boundaries and forwarding methods in the program, and to
+// synthesize the interfaces and emulated dispatch classes in the desugared library.
+public class AutoCloseableRetargeterPostProcessor implements CfPostProcessingDesugaring {
+
+ private final AppView<?> appView;
+ private final AutoCloseableRetargeterHelper data;
+
+ public AutoCloseableRetargeterPostProcessor(AppView<?> appView) {
+ this.appView = appView;
+ this.data =
+ new AutoCloseableRetargeterHelper(
+ appView.options().getMinApiLevel(), appView.dexItemFactory());
+ }
+
+ @Override
+ public void postProcessingDesugaring(
+ Collection<DexProgramClass> programClasses,
+ CfPostProcessingDesugaringEventConsumer eventConsumer,
+ ExecutorService executorService) {
+ ensureInterfacesAndForwardingMethodsSynthesized(programClasses, eventConsumer);
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private void ensureInterfacesAndForwardingMethodsSynthesized(
+ Collection<DexProgramClass> programClasses,
+ AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer) {
+ ProcessorContext processorContext = appView.createProcessorContext();
+ MainThreadContext mainThreadContext = processorContext.createMainThreadContext();
+ for (DexProgramClass clazz : programClasses) {
+ if (clazz.superType == null) {
+ assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+ continue;
+ }
+ if (implementsAutoCloseableAtLibraryBoundary(clazz)) {
+ ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer, clazz, mainThreadContext);
+ }
+ }
+ }
+
+ private boolean implementsAutoCloseableAtLibraryBoundary(DexProgramClass clazz) {
+ if (clazz.interfaces.contains(appView.dexItemFactory().autoCloseableType)) {
+ return true;
+ }
+ WorkList<DexType> workList = collectLibrarySuperTypeAndInterfaces(clazz);
+ return libraryTypesImplementsAutoCloseable(workList, clazz);
+ }
+
+ private WorkList<DexType> collectLibrarySuperTypeAndInterfaces(DexProgramClass clazz) {
+ WorkList<DexType> workList = WorkList.newIdentityWorkList();
+ DexClass superclass = appView.definitionFor(clazz.superType);
+ // Only performs computation if superclass is a library class, but not object to filter out
+ // the most common case.
+ if (superclass != null
+ && superclass.isLibraryClass()
+ && !superclass.type.isIdenticalTo(appView.dexItemFactory().objectType)) {
+ workList.addIfNotSeen(superclass.type);
+ }
+ for (DexType itf : clazz.interfaces) {
+ DexClass superItf = appView.definitionFor(itf);
+ if (superItf != null) {
+ workList.addIfNotSeen(superItf.type);
+ }
+ }
+ return workList;
+ }
+
+ private boolean libraryTypesImplementsAutoCloseable(
+ WorkList<DexType> workList, DexProgramClass clazz) {
+ while (workList.hasNext()) {
+ DexType current = workList.next();
+ if (current.isIdenticalTo(appView.dexItemFactory().objectType)) {
+ continue;
+ }
+ DexClass currentClass = appView.definitionFor(current);
+ if (currentClass == null) {
+ reportInvalidSupertype(current, clazz);
+ continue;
+ }
+ if (currentClass.interfaces.contains(appView.dexItemFactory().autoCloseableType)) {
+ return true;
+ }
+ workList.addIfNotSeen(currentClass.superType);
+ workList.addIfNotSeen(currentClass.interfaces);
+ }
+ return false;
+ }
+
+ private void reportInvalidSupertype(DexType current, DexProgramClass origin) {
+ appView
+ .options()
+ .warningInvalidLibrarySuperclassForDesugar(
+ origin.getOrigin(),
+ origin.type,
+ current,
+ "missing",
+ ImmutableSet.of(getClose(origin.type)));
+ }
+
+ private void ensureInterfacesAndForwardingMethodsSynthesized(
+ AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer,
+ DexProgramClass clazz,
+ MainThreadContext mainThreadContext) {
+ // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
+ // methods.
+ // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+ // applies up to 24.
+ if (appView.isAlreadyLibraryDesugared(clazz)) {
+ return;
+ }
+ DexMethod close = getClose(clazz.type);
+ if (clazz.lookupVirtualMethod(close) == null) {
+ DexEncodedMethod newMethod =
+ createForwardingMethod(close, clazz, eventConsumer, mainThreadContext);
+ if (newMethod == null) {
+ // We don't support desugaring on all subtypes, in which case there is not need to inject
+ // the interface.
+ return;
+ }
+ clazz.addVirtualMethod(newMethod);
+ eventConsumer.acceptAutoCloseableForwardingMethod(new ProgramMethod(clazz, newMethod), clazz);
+ }
+ DexType autoCloseableType = appView.dexItemFactory().autoCloseableType;
+ if (clazz.interfaces.contains(autoCloseableType)) {
+ return;
+ }
+ clazz.addExtraInterfaces(
+ Collections.singletonList(new ClassTypeSignature(autoCloseableType)),
+ appView.dexItemFactory());
+ eventConsumer.acceptAutoCloseableInterfaceInjection(
+ clazz, appView.definitionFor(autoCloseableType));
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private DexEncodedMethod createForwardingMethod(
+ DexMethod target,
+ DexProgramClass clazz,
+ AutoCloseableRetargeterPostProcessingEventConsumer eventConsumer,
+ MainThreadContext mainThreadContext) {
+ // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
+ // even if this results in invalid code, these classes are never desugared.
+ // In desugared library, emulated interface methods can be overridden by retarget lib members.
+ AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+ assert clazz.lookupVirtualMethod(target) == null;
+ DexClassAndMethod superMethod = lookupSuperIncludingInterfaces(appView, target, clazz);
+ if (superMethod == null
+ || !data.superTargetsToRewrite().contains(superMethod.getHolderType())) {
+ return null;
+ }
+ DexMethod forwardMethod =
+ data.synthesizeDispatchCase(
+ appView,
+ superMethod.getHolderType(),
+ clazz,
+ eventConsumer,
+ () -> mainThreadContext.createUniqueContext(clazz));
+ assert forwardMethod != null && forwardMethod != target;
+ DexClassAndMethod resolvedMethod =
+ appInfoForDesugaring
+ .resolveMethodLegacy(target, target.getHolderType().isInterface(appInfoForDesugaring))
+ .getResolutionPair();
+ assert resolvedMethod != null;
+ DexEncodedMethod desugaringForwardingMethod =
+ DexEncodedMethod.createDesugaringForwardingMethod(
+ resolvedMethod,
+ clazz,
+ forwardMethod,
+ appView.dexItemFactory(),
+ appView.getSyntheticItems().isSynthetic(forwardMethod.getHolderType()));
+ desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+ return desugaringForwardingMethod;
+ }
+
+ private DexMethod getClose(DexType holder) {
+ return appView
+ .dexItemFactory()
+ .createMethod(
+ holder,
+ appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
+ "close");
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
index c4be060..fa05f19 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -161,7 +161,7 @@
assert resolvedMethod != null;
DexEncodedMethod desugaringForwardingMethod =
DexEncodedMethod.createDesugaringForwardingMethod(
- resolvedMethod, clazz, forwardMethod, appView.dexItemFactory());
+ resolvedMethod, clazz, forwardMethod, appView.dexItemFactory(), true);
desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
return desugaringForwardingMethod;
}
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/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index c2a1998..2b89cbb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -1038,7 +1038,7 @@
// In desugared library, emulated interface methods can be overridden by retarget lib members.
DexEncodedMethod desugaringForwardingMethod =
DexEncodedMethod.createDesugaringForwardingMethod(
- target, clazz, forwardMethod, dexItemFactory);
+ target, clazz, forwardMethod, dexItemFactory, true);
if (!target.isProgramMethod() || target.getDefinition().isLibraryMethodOverride().isTrue()) {
desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
}
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..2604cef 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,11 +47,13 @@
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
@@ -89,8 +100,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 +108,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 +139,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/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index b8c5959..77a8cd8 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -67,6 +67,7 @@
private InvokeType invokeType = null;
private Boolean isInterface = null;
private boolean castResult = false;
+ private boolean ignoreTargetResult = false;
private boolean isConstructorDelegate = false;
private AppInfoWithClassHierarchy appInfoForCastArguments = null;
@@ -157,6 +158,11 @@
return this;
}
+ public ForwardMethodBuilder setIgnoreTargetResult() {
+ ignoreTargetResult = true;
+ return this;
+ }
+
public ForwardMethodBuilder setCastArguments(AppInfoWithClassHierarchy appInfo) {
appInfoForCastArguments = appInfo;
return this;
@@ -380,6 +386,9 @@
&& !targetMethod.getReturnType().isVoidType()) {
assert ValueType.fromDexType(sourceMethod.getReturnType())
== ValueType.fromDexType(targetMethod.getReturnType());
+ } else if (ignoreTargetResult) {
+ assert sourceMethod.getReturnType().isVoidType();
+ assert !targetMethod.getReturnType().isVoidType();
} else {
assert sourceMethod.getReturnType() == targetMethod.getReturnType();
}
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
index 7fa618c..2882170 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ConcreteProfileCollectionAdditions.java
@@ -80,6 +80,12 @@
context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
}
+ public void addMethodAndHolderIfContextIsInProfile(
+ ProgramMethod method, ProgramDefinition context) {
+ applyIfContextIsInProfile(
+ context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+ }
+
void applyIfContextIsInProfile(
ProgramDefinition context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
if (context.isProgramClass()) {
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..8a65064 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,12 @@
}
@Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition 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/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
index 944f860..926c9d7 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfPostProcessingDesugaringEventConsumer.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
@@ -106,11 +107,28 @@
}
@Override
+ public void acceptAutoCloseableForwardingMethod(ProgramMethod method, ProgramDefinition context) {
+ additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+ parent.acceptAutoCloseableForwardingMethod(method, context);
+ }
+
+ @Override
+ public void acceptAutoCloseableDispatchMethod(ProgramMethod method, ProgramDefinition context) {
+ additionsCollection.addMethodAndHolderIfContextIsInProfile(method, context);
+ parent.acceptAutoCloseableDispatchMethod(method, context);
+ }
+
+ @Override
public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
parent.acceptInterfaceInjection(clazz, newInterface);
}
@Override
+ public void acceptAutoCloseableInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
+ parent.acceptAutoCloseableInterfaceInjection(clazz, newInterface);
+ }
+
+ @Override
public void acceptInterfaceMethodDesugaringForwardingMethod(
ProgramMethod method, DexClassAndMethod baseMethod) {
additionsCollection.addMethodIfContextIsInProfile(method, baseMethod);
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..1ef8edc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -66,6 +66,10 @@
public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy");
// Method synthetics.
+ public final SyntheticKind AUTOCLOSEABLE_DISPATCHER =
+ generator.forSingleMethodWithGlobalMerging("AutoCloseableDispatcher");
+ public final SyntheticKind AUTOCLOSEABLE_FORWARDER =
+ generator.forSingleMethodWithGlobalMerging("AutoCloseableForwarder");
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 6dd1d81..64de864 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;
@@ -2695,6 +2696,17 @@
&& !canUseDefaultAndStaticInterfaceMethods();
}
+ public boolean canHaveMissingImplementsAutoCloseableInterface() {
+ return getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.V);
+ }
+
+ public boolean shouldDesugarAutoCloseable() {
+ return desugarState.isOn()
+ && getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+ && canHaveMissingImplementsAutoCloseableInterface()
+ && 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/AutoCloseableRetargeterAndroidSubtypeTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java
new file mode 100644
index 0000000..5fca53f
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTest.java
@@ -0,0 +1,271 @@
+// 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.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.android.tools.r8.utils.DescriptorUtils;
+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 AutoCloseableRetargeterAndroidSubtypeTest extends AbstractBackportTest {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public AutoCloseableRetargeterAndroidSubtypeTest(TestParameters parameters) throws IOException {
+ super(
+ parameters,
+ getTestRunner(),
+ ImmutableList.of(
+ getTestRunner(),
+ getTransformedSubclass(
+ ContentProviderClientOverride.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ ContentProviderClientSub.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientOverride.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientSub.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverOverride.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverSub.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ TypedArrayOverride.class,
+ DexItemFactory.androidContentResTypedArrayDescriptorString),
+ getTransformedSubclass(
+ TypedArraySub.class, DexItemFactory.androidContentResTypedArrayDescriptorString)));
+
+ // 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, 4);
+ }
+
+ @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[] getTransformedSubclass(Class<?> sub, String superDesc) throws IOException {
+ return transformer(sub)
+ .setSuper(superDesc)
+ .transformMethodInsnInMethod(
+ "<init>",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ assert name.equals("<init>");
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ })
+ .transformMethodInsnInMethod(
+ "close",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("close")) {
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .clearNest()
+ .transform();
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(ContentProviderClient.class),
+ DexItemFactory.androidContentContentProviderClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(DrmManagerClient.class),
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaMetadataRetriever.class),
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TypedArray.class),
+ DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .clearNest()
+ .transform();
+ }
+
+ public static class ContentProviderClientOverride extends ContentProviderClient {
+
+ public void close() {
+ super.close();
+ System.out.println("close content provider");
+ }
+ }
+
+ public static class ContentProviderClientSub extends ContentProviderClient {}
+
+ public static class DrmManagerClientOverride extends DrmManagerClient {
+
+ public void close() {
+ super.close();
+ System.out.println("close drm manager");
+ }
+ }
+
+ public static class DrmManagerClientSub extends DrmManagerClient {}
+
+ public static class MediaMetadataRetrieverOverride extends MediaMetadataRetriever {
+
+ public void close() {
+ super.close();
+ System.out.println("close media metadata");
+ }
+ }
+
+ public static class MediaMetadataRetrieverSub extends MediaMetadataRetriever {}
+
+ public static class TypedArrayOverride extends TypedArray {
+
+ public void close() {
+ super.close();
+ System.out.println("close typed array");
+ }
+ }
+
+ public static class TypedArraySub extends TypedArray {}
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) {
+ contentProviderClient();
+ drmManagerClient();
+ mediaMetadataRetriever();
+ typedArray();
+ }
+
+ private static void contentProviderClient() {
+ ContentProviderClientSub cpcSub = new ContentProviderClientSub();
+ MiniAssert.assertFalse(cpcSub.wasClosed);
+ cpcSub.close();
+ MiniAssert.assertTrue(cpcSub.wasClosed);
+
+ ContentProviderClientOverride cpcOverride = new ContentProviderClientOverride();
+ MiniAssert.assertFalse(cpcOverride.wasClosed);
+ cpcOverride.close();
+ MiniAssert.assertTrue(cpcOverride.wasClosed);
+ }
+
+ public static void drmManagerClient() {
+ DrmManagerClientSub drmMC = new DrmManagerClientSub();
+ MiniAssert.assertFalse(drmMC.wasClosed);
+ drmMC.close();
+ MiniAssert.assertTrue(drmMC.wasClosed);
+
+ DrmManagerClientOverride drmMCOverride = new DrmManagerClientOverride();
+ MiniAssert.assertFalse(drmMCOverride.wasClosed);
+ drmMCOverride.close();
+ MiniAssert.assertTrue(drmMCOverride.wasClosed);
+ }
+
+ public static void mediaMetadataRetriever() {
+ MediaMetadataRetrieverSub mmrSub = new MediaMetadataRetrieverSub();
+ MiniAssert.assertFalse(mmrSub.wasClosed);
+ mmrSub.close();
+ MiniAssert.assertTrue(mmrSub.wasClosed);
+
+ MediaMetadataRetrieverOverride mmrOverride = new MediaMetadataRetrieverOverride();
+ MiniAssert.assertFalse(mmrOverride.wasClosed);
+ mmrOverride.close();
+ MiniAssert.assertTrue(mmrOverride.wasClosed);
+ }
+
+ public static void typedArray() {
+ TypedArraySub taSub = new TypedArraySub();
+ MiniAssert.assertFalse(taSub.wasClosed);
+ taSub.close();
+ MiniAssert.assertTrue(taSub.wasClosed);
+
+ TypedArrayOverride taOverride = new TypedArrayOverride();
+ MiniAssert.assertFalse(taOverride.wasClosed);
+ taOverride.close();
+ MiniAssert.assertTrue(taOverride.wasClosed);
+ }
+
+ // Forwards to MiniAssert to avoid having to make it public.
+ public static void doFail(String message) {
+ MiniAssert.fail(message);
+ }
+ }
+}
diff --git a/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java
new file mode 100644
index 0000000..efad24f
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterAndroidSubtypeTwrTest.java
@@ -0,0 +1,330 @@
+// 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.ContentProviderClientApiLevel24;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.DrmManagerClientApiLevel24;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaDrmApiLevel28;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.MediaMetadataRetrieverApiLevel29;
+import com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.TypedArrayAndroidApiLevel31;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+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 AutoCloseableRetargeterAndroidSubtypeTwrTest extends AbstractBackportTest {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public AutoCloseableRetargeterAndroidSubtypeTwrTest(TestParameters parameters)
+ throws IOException {
+ super(
+ parameters,
+ getTestRunner(),
+ ImmutableList.of(
+ getTestRunner(),
+ getTransformedSubclass(
+ ContentProviderClientOverride.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ ContentProviderClientSub.class,
+ DexItemFactory.androidContentContentProviderClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientOverride.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ DrmManagerClientSub.class,
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverOverride.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ MediaMetadataRetrieverSub.class,
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString),
+ getTransformedSubclass(
+ TypedArrayOverride.class,
+ DexItemFactory.androidContentResTypedArrayDescriptorString),
+ getTransformedSubclass(
+ TypedArraySub.class, DexItemFactory.androidContentResTypedArrayDescriptorString)));
+
+ // 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[] getTransformedSubclass(Class<?> sub, String superDesc) throws IOException {
+ return transformer(sub)
+ .setSuper(superDesc)
+ .transformMethodInsnInMethod(
+ "<init>",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ assert name.equals("<init>");
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ })
+ .transformMethodInsnInMethod(
+ "close",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("close")) {
+ visitor.visitMethodInsn(
+ opcode,
+ DescriptorUtils.descriptorToInternalName(superDesc),
+ name,
+ descriptor,
+ isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .clearNest()
+ .transform();
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(ContentProviderClientApiLevel24.class),
+ DexItemFactory.androidContentContentProviderClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(DrmManagerClientApiLevel24.class),
+ DexItemFactory.androidDrmDrmManagerClientDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaMetadataRetrieverApiLevel29.class),
+ DexItemFactory.androidMediaMediaMetadataRetrieverDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TypedArrayAndroidApiLevel31.class),
+ DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(MediaDrmApiLevel28.class),
+ DexItemFactory.androidMediaMediaDrmDescriptorString)
+ .clearNest()
+ .transform();
+ }
+
+ public static class ContentProviderClientOverride extends ContentProviderClientApiLevel24 {
+
+ public void close() {
+ super.close();
+ System.out.println("close content provider");
+ }
+ }
+
+ public static class ContentProviderClientSub extends ContentProviderClientApiLevel24 {}
+
+ public static class DrmManagerClientOverride extends DrmManagerClientApiLevel24 {
+
+ public void close() {
+ super.close();
+ System.out.println("close drm manager");
+ }
+ }
+
+ public static class DrmManagerClientSub extends DrmManagerClientApiLevel24 {}
+
+ public static class MediaMetadataRetrieverOverride extends MediaMetadataRetrieverApiLevel29 {
+
+ public void close() {
+ super.close();
+ System.out.println("close media metadata");
+ }
+ }
+
+ public static class MediaMetadataRetrieverSub extends MediaMetadataRetrieverApiLevel29 {}
+
+ public static class TypedArrayOverride extends TypedArrayAndroidApiLevel31 {
+
+ public void close() {
+ super.close();
+ System.out.println("close typed array");
+ }
+ }
+
+ public static class TypedArraySub extends TypedArrayAndroidApiLevel31 {}
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) {
+ raw();
+ contentProviderClient();
+ drmManagerClient();
+ mediaMetadataRetriever();
+ typedArray();
+ }
+
+ private static void raw() {
+ ContentProviderClientApiLevel24[] box1 = new ContentProviderClientApiLevel24[1];
+ try (ContentProviderClientApiLevel24 val = new ContentProviderClientApiLevel24()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ DrmManagerClientApiLevel24[] box2 = new DrmManagerClientApiLevel24[1];
+ try (DrmManagerClientApiLevel24 val = new DrmManagerClientApiLevel24()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+
+ MediaDrmApiLevel28[] box3 = new MediaDrmApiLevel28[1];
+ try (MediaDrmApiLevel28 val = new MediaDrmApiLevel28()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box3[0] = val;
+ }
+ MiniAssert.assertTrue(box3[0].wasClosed);
+
+ MediaMetadataRetrieverApiLevel29[] box4 = new MediaMetadataRetrieverApiLevel29[1];
+ try (MediaMetadataRetrieverApiLevel29 val = new MediaMetadataRetrieverApiLevel29()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box4[0] = val;
+ }
+ MiniAssert.assertTrue(box4[0].wasClosed);
+
+ TypedArrayAndroidApiLevel31[] box5 = new TypedArrayAndroidApiLevel31[1];
+ try (TypedArrayAndroidApiLevel31 val = new TypedArrayAndroidApiLevel31()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box5[0] = val;
+ }
+ MiniAssert.assertTrue(box5[0].wasClosed);
+ }
+
+ private static void contentProviderClient() {
+ ContentProviderClientSub[] box1 = new ContentProviderClientSub[1];
+ try (ContentProviderClientSub val = new ContentProviderClientSub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ ContentProviderClientOverride[] box2 = new ContentProviderClientOverride[1];
+ try (ContentProviderClientOverride val = new ContentProviderClientOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ public static void drmManagerClient() {
+ DrmManagerClientSub[] box1 = new DrmManagerClientSub[1];
+ try (DrmManagerClientSub val = new DrmManagerClientSub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ DrmManagerClientOverride[] box2 = new DrmManagerClientOverride[1];
+ try (DrmManagerClientOverride val = new DrmManagerClientOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ public static void mediaMetadataRetriever() {
+ MediaMetadataRetrieverSub[] box1 = new MediaMetadataRetrieverSub[1];
+ try (MediaMetadataRetrieverSub val = new MediaMetadataRetrieverSub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ MediaMetadataRetrieverOverride[] box2 = new MediaMetadataRetrieverOverride[1];
+ try (MediaMetadataRetrieverOverride val = new MediaMetadataRetrieverOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ public static void typedArray() {
+ TypedArraySub[] box1 = new TypedArraySub[1];
+ try (TypedArraySub val = new TypedArraySub()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box1[0] = val;
+ }
+ MiniAssert.assertTrue(box1[0].wasClosed);
+
+ TypedArrayOverride[] box2 = new TypedArrayOverride[1];
+ try (TypedArrayOverride val = new TypedArrayOverride()) {
+ MiniAssert.assertFalse(val.wasClosed);
+ box2[0] = val;
+ }
+ MiniAssert.assertTrue(box2[0].wasClosed);
+ }
+
+ // Forwards to MiniAssert to avoid having to make it public.
+ public static void doFail(String message) {
+ MiniAssert.fail(message);
+ }
+ }
+}
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/AutoCloseableRetargeterExecutorServiceSubtypeTest.java b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java
new file mode 100644
index 0000000..d9c567c
--- /dev/null
+++ b/src/test/examplesJava21/autocloseable/AutoCloseableRetargeterExecutorServiceSubtypeTest.java
@@ -0,0 +1,349 @@
+// 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 static org.junit.Assume.assumeTrue;
+
+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.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.Assert;
+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 AutoCloseableRetargeterExecutorServiceSubtypeTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntime(CfVm.JDK21)
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+ .build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines("close", "close", "close", "close", "close", "close", "close", "SUCCESS");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ 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)))
+ .inspect(this::assertCloseMethodsAndTags)
+ .run(
+ parameters.getRuntime(),
+ Main.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void assertCloseMethodsAndTags(CodeInspector inspector) {
+ for (Class<? extends ExecutorService> clazz :
+ ImmutableList.of(
+ PrintForkJoinPool.class,
+ OverrideForkJoinPool.class,
+ Executor1.class,
+ Executor2.class)) {
+ ClassSubject subj = inspector.clazz(clazz);
+ Assert.assertTrue(subj.isPresent());
+ Assert.assertTrue(subj.allMethods().stream().anyMatch(m -> m.getFinalName().equals("close")));
+ Assert.assertTrue(
+ subj.getDexProgramClass()
+ .getInterfaces()
+ .contains(inspector.getFactory().autoCloseableType));
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ 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(EXPECTED_OUTPUT);
+ }
+
+ public static class PrintForkJoinPool extends ForkJoinPool {
+
+ public void close() {
+ super.close();
+ System.out.println("close");
+ }
+ }
+
+ public static class OverrideForkJoinPool extends ForkJoinPool {}
+
+ public static class OverrideForkJoinPool2 extends OverrideForkJoinPool {}
+
+ public static class Executor1 implements ExecutorService {
+
+ boolean done = false;
+
+ @Override
+ public void shutdown() {
+ done = true;
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ done = true;
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return done;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return done;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return true;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {}
+ }
+
+ public static class Executor2 implements ExecutorService {
+
+ boolean done = false;
+
+ @Override
+ public void shutdown() {
+ done = true;
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ done = true;
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return done;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return done;
+ }
+
+ public void close() {
+ ExecutorService.super.close();
+ System.out.println("close");
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return true;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {}
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ PrintForkJoinPool forkJoinPool = new PrintForkJoinPool();
+ forkJoinPool.close();
+ forkJoinPool = new PrintForkJoinPool();
+ close(forkJoinPool);
+
+ ForkJoinPool forkJoinPool2 = new PrintForkJoinPool();
+ forkJoinPool2.close();
+
+ ExecutorService executorService = new PrintForkJoinPool();
+ executorService.close();
+ executorService = new PrintForkJoinPool();
+ closeExecutorService(executorService);
+
+ OverrideForkJoinPool overrideForkJoinPool = new OverrideForkJoinPool();
+ overrideForkJoinPool.close();
+ overrideForkJoinPool = new OverrideForkJoinPool();
+ close(overrideForkJoinPool);
+
+ ForkJoinPool overrideForkJoinPool1 = new OverrideForkJoinPool();
+ overrideForkJoinPool1.close();
+
+ executorService = new OverrideForkJoinPool();
+ executorService.close();
+ executorService = new OverrideForkJoinPool();
+ closeExecutorService(executorService);
+
+ OverrideForkJoinPool2 overrideForkJoinPool2 = new OverrideForkJoinPool2();
+ overrideForkJoinPool2.close();
+ overrideForkJoinPool2 = new OverrideForkJoinPool2();
+ close(overrideForkJoinPool2);
+
+ ForkJoinPool overrideForkJoinPool22 = new OverrideForkJoinPool2();
+ overrideForkJoinPool22.close();
+
+ executorService = new OverrideForkJoinPool2();
+ executorService.close();
+ executorService = new OverrideForkJoinPool2();
+ closeExecutorService(executorService);
+
+ Executor1 executor1 = new Executor1();
+ executor1.close();
+ Executor2 executor2 = new Executor2();
+ executor2.close();
+ ExecutorService executor11 = new Executor1();
+ close(executor11);
+ ExecutorService executor22 = new Executor2();
+ close(executor22);
+
+ 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/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/examplesJava21/twr/ExecutorServiceBackportTest.java b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
index c55a8e4..a57d54b 100644
--- a/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
+++ b/src/test/examplesJava21/twr/ExecutorServiceBackportTest.java
@@ -4,8 +4,14 @@
package twr;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.compileAutoCloseableAndroidLibraryClasses;
+import static com.android.tools.r8.desugar.AutoCloseableAndroidLibraryFileData.getAutoCloseableAndroidClassData;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.desugar.backports.AbstractBackportTest;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -33,6 +39,32 @@
ignoreInvokes("isTerminated");
}
+ @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));
+ }
+ }
+
+ @Override
+ public void testD8Cf() throws Exception {
+ super.testD8Cf();
+ }
+
public static class Main {
public static void main(String[] args) {
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..cdbf02f 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,8 +261,10 @@
testForD8(Backend.CF)
.setMinApi(parameters)
.apply(this::configureProgram)
+ .apply(this::configureD8Options)
.setIncludeClassesChecksum(true)
.compileWithExpectedDiagnostics(this::checkDiagnostics)
+ .apply(this::configure)
.run(parameters.getRuntime(), testClassName)
.assertSuccess()
.inspect(this::assertDesugaring);