blob: 64ebfd20e7f37fadea342a8789be80edda7d669c [file] [log] [blame]
// 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.isPossiblyTrue() && 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());
}
}