Find all classes requiring desugaring of AutoCloseable#close()
Bug: b/369520931
Change-Id: Id233d3b46cbc18efeaba730ede1c4bf50e051e85
diff --git a/src/test/examplesJava21/twr/LookUpCloseResourceTest.java b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
new file mode 100644
index 0000000..98f088d
--- /dev/null
+++ b/src/test/examplesJava21/twr/LookUpCloseResourceTest.java
@@ -0,0 +1,176 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package twr;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+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.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.DexItemFactory;
+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.DirectMappedDexApplication;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+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.OptionalBool;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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 LookUpCloseResourceTest extends TestBase {
+
+ private static final boolean DEBUG_PRINT = false;
+ private static int MAX_PROCESSED_ANDROID_API_LEVEL = 36;
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ InternalOptions options = new InternalOptions();
+ options
+ .getArtProfileOptions()
+ .setAllowReadingEmptyArtProfileProvidersMultipleTimesForTesting(true);
+ DexItemFactory factory = options.dexItemFactory();
+ DexMethod close =
+ factory.createMethod(
+ factory.autoCloseableType, factory.createProto(factory.voidType), "close");
+
+ Map<DexType, DexType> toSuper = new IdentityHashMap<>();
+ AppView<?> appViewForMax = getAppInfo(options, MAX_PROCESSED_ANDROID_API_LEVEL);
+ AppInfoWithClassHierarchy appInfoForMax = appViewForMax.appInfoForDesugaring();
+ List<DexType> autoCloseableSubclassesWithOverride = new ArrayList<>();
+ for (DexProgramClass clazz : appInfoForMax.classes()) {
+ OptionalBool contains =
+ appInfoForMax.implementedInterfaces(clazz.getType()).contains(factory.autoCloseableType);
+ if (contains.isTrue() && clazz.getType() != factory.autoCloseableType) {
+ if (clazz.lookupVirtualMethod(close.withHolder(clazz.getType(), factory)) != null) {
+ autoCloseableSubclassesWithOverride.add(clazz.getType());
+ } else {
+ DexClassAndMethod superResult =
+ appInfoForMax.lookupSuperTarget(
+ close.withHolder(clazz.getType(), factory), clazz, appViewForMax, appInfoForMax);
+ if (superResult == null) {
+ superResult =
+ appInfoForMax.lookupMaximallySpecificMethod(
+ clazz, close.withHolder(clazz.getType(), factory));
+ }
+ // Abstract class/itf may directly rely on AutoCloseable#close().
+ assert superResult != null || clazz.isAbstract();
+ if (superResult != null) {
+ toSuper.put(clazz.getType(), superResult.getHolderType());
+ }
+ }
+ }
+ }
+
+ Map<DexType, AndroidApiLevel> classIntroducedBeforeClose = new IdentityHashMap<>();
+ for (int i = MAX_PROCESSED_ANDROID_API_LEVEL - 1; i > 0; i--) {
+ if (ToolHelper.hasAndroidJar(AndroidApiLevel.getAndroidApiLevel(i))) {
+ AppView<?> appView = getAppInfo(options, i);
+ AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+ for (DexType type : autoCloseableSubclassesWithOverride) {
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null
+ && clazz.lookupVirtualMethod(close) == null
+ && !classIntroducedBeforeClose.containsKey(type)) {
+ classIntroducedBeforeClose.put(type, AndroidApiLevel.getAndroidApiLevel(i));
+ }
+ }
+ }
+ }
+
+ List<DexMethod> closeBackports = new ArrayList<>();
+ BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+ appInfoForMax.app(),
+ options,
+ m -> {
+ if (m.getName().toString().equals("close")) {
+ closeBackports.add(m);
+ }
+ },
+ f -> {});
+
+ Set<DexType> toRemove = Sets.newIdentityHashSet();
+ toSuper.forEach(
+ (k, v) -> {
+ if (!classIntroducedBeforeClose.containsKey(v)) {
+ toRemove.add(k);
+ }
+ });
+ for (DexType dexType : toRemove) {
+ toSuper.remove(dexType);
+ }
+
+ Assert.assertEquals(10, toSuper.size());
+ Assert.assertEquals(11, classIntroducedBeforeClose.size());
+ Assert.assertEquals(5, closeBackports.size());
+
+ if (DEBUG_PRINT) {
+ print(closeBackports, classIntroducedBeforeClose, toSuper);
+ }
+ }
+
+ private void print(
+ List<DexMethod> closeBackports,
+ Map<DexType, AndroidApiLevel> classIntroducedBeforeClose,
+ Map<DexType, DexType> toSuper) {
+ Map<DexType, List<DexType>> toSub = new IdentityHashMap<>();
+ toSuper.forEach(
+ (sup, sub) -> {
+ toSub.computeIfAbsent(sub, k -> new ArrayList<>()).add(sup);
+ });
+ System.out.println("Classes introduced in android.jar before their close() method override :");
+ classIntroducedBeforeClose.forEach(
+ (type, api) -> {
+ System.out.print(type + " ");
+ if (toSub.containsKey(type)) {
+ System.out.print("[");
+ for (DexType sub : toSub.get(type)) {
+ System.out.print(sub + ", ");
+ }
+ System.out.print("] ");
+ }
+ if (closeBackports.stream().anyMatch(m -> m.getHolderType() == type)) {
+ System.out.print("-- backport");
+ }
+ System.out.println();
+ });
+ }
+
+ private AppView<?> getAppInfo(InternalOptions options, int api) throws IOException {
+ AndroidApp app = AndroidApp.builder().addProgramFile(ToolHelper.getAndroidJar(api)).build();
+ DirectMappedDexApplication libHolder =
+ new ApplicationReader(app, options, Timing.empty()).read().toDirect();
+ AppInfo initialAppInfo =
+ AppInfo.createInitialAppInfo(libHolder, GlobalSyntheticsStrategy.forNonSynthesizing());
+ return AppView.createForD8(initialAppInfo, options.getTypeRewriter(), Timing.empty());
+ }
+}