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());
+  }
+}