| // 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.ImmutableSet; |
| 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 Set<String> CANNOT_FIX = |
| ImmutableSet.of( |
| "java.net.URLClassLoader", |
| "android.net.wifi.p2p.WifiP2pManager$Channel", |
| "android.content.res.AssetFileDescriptor$AutoCloseInputStream"); |
| private static final Set<String> TOO_OLD_TO_FIX = |
| ImmutableSet.of("java.nio.channels.FileLock", "android.database.sqlite.SQLiteClosable"); |
| private static final boolean DEBUG_PRINT = false; |
| private static int MAX_PROCESSED_ANDROID_API_LEVEL = 36; |
| |
| @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(9, toSuper.size()); |
| Assert.assertEquals(12, classIntroducedBeforeClose.size()); |
| Assert.assertEquals(6, closeBackports.size()); |
| |
| if (DEBUG_PRINT) { |
| print(closeBackports, classIntroducedBeforeClose, toSuper, appViewForMax); |
| } |
| } |
| |
| private void print( |
| List<DexMethod> closeBackports, |
| Map<DexType, AndroidApiLevel> classIntroducedBeforeClose, |
| Map<DexType, DexType> toSuper, |
| AppView<?> appView) { |
| 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(api + " "); |
| System.out.print(appView.definitionFor(type).isFinal() ? "f " : "nf "); |
| System.out.print(type + " "); |
| if (closeBackports.stream().anyMatch(m -> m.getHolderType() == type)) { |
| System.out.print("-- backport"); |
| } |
| if (CANNOT_FIX.contains(type.toString())) { |
| System.out.print("-- cannotfix"); |
| } |
| if (TOO_OLD_TO_FIX.contains(type.toString())) { |
| System.out.print("-- tooOldToFix"); |
| } |
| System.out.println(); |
| if (toSub.containsKey(type)) { |
| System.out.print("["); |
| for (DexType sub : toSub.get(type)) { |
| System.out.print(sub + ", "); |
| } |
| System.out.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.getLibraryDesugaringOptions().getTypeRewriter(), Timing.empty()); |
| } |
| } |