blob: b15cca9b15457007a846cfbdaeb9858e43d54d06 [file] [log] [blame]
// Copyright (c) 2021, 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.optimize.instanceofremoval;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
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.AppInfoWithClassHierarchy;
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.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
import com.android.tools.r8.ir.analysis.type.Nullability;
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.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.file.Path;
import java.util.jar.JarFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ZipFileInstanceOfAutoCloseableTest extends TestBase {
static final String EXPECTED_PRE_API_19 = StringUtils.lines("Not an AutoCloseable");
static final String EXPECTED_POST_API_19 = StringUtils.lines("Is an AutoCloseable");
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public ZipFileInstanceOfAutoCloseableTest(TestParameters parameters) {
this.parameters = parameters;
}
private boolean runtimeZipFileIsCloseable() {
return parameters.isCfRuntime()
|| parameters
.getRuntime()
.asDex()
.maxSupportedApiLevel()
.isGreaterThanOrEqualTo(AndroidApiLevel.K);
}
private String expectedOutput() {
return runtimeZipFileIsCloseable() ? EXPECTED_POST_API_19 : EXPECTED_PRE_API_19;
}
private Path getAndroidJar() {
// Always use an android jar later than API 19. Thus at compile-time ZipFile < Closeable.
return ToolHelper.getAndroidJar(AndroidApiLevel.LATEST);
}
private String getZipFile() throws IOException {
return ZipBuilder.builder(temp.newFile("file.zip").toPath())
.addBytes("entry", new byte[1])
.build()
.toString();
}
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
testForD8()
.addInnerClasses(ZipFileInstanceOfAutoCloseableTest.class)
.setMinApi(parameters.getApiLevel())
.addLibraryFiles(getAndroidJar())
.run(parameters.getRuntime(), TestClass.class, getZipFile())
.assertSuccessWithOutput(expectedOutput());
}
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(ZipFileInstanceOfAutoCloseableTest.class)
.addKeepMainRule(TestClass.class)
.setMinApi(parameters.getApiLevel())
.addLibraryFiles(getAndroidJar())
.run(parameters.getRuntime(), TestClass.class, getZipFile())
.assertSuccessWithOutput(expectedOutput());
}
@Test
public void testTypeStructure() throws Exception {
assumeTrue(parameters.isDexRuntime());
// Set the min API and create the raw app.
InternalOptions options = new InternalOptions();
options.minApiLevel = parameters.getApiLevel().getLevel();
DirectMappedDexApplication application =
new ApplicationReader(
AndroidApp.builder()
.addProgramFiles(ToolHelper.getClassFileForTestClass(MyJarFileScanner.class))
.addLibraryFiles(getAndroidJar())
.build(),
options,
Timing.empty())
.read()
.toDirect();
AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
// Type references.
DexType zipFileType = appView.dexItemFactory().createType("Ljava/util/zip/ZipFile;");
DexType closeableType = appView.dexItemFactory().createType("Ljava/io/Closeable;");
DexType autoCloseableType = appView.dexItemFactory().createType("Ljava/lang/AutoCloseable;");
DexType scannerType = appView.dexItemFactory().createType("Ljava/util/Scanner;");
DexType myJarFileChannel = buildType(MyJarFileScanner.class, appView.dexItemFactory());
// Read the computed interface types. Both the "type element" which is used in IR and the full
// computed dependencies.
ClassTypeElement zipFileTypeElement = makeTypeElement(appView, zipFileType);
{
InterfaceCollection directInterfaces = zipFileTypeElement.getInterfaces();
InterfaceCollection allInterfaces = appView.appInfo().implementedInterfaces(zipFileType);
if (zipFileHasCloseable()) {
// After API 19 / K the types are known and present.
assertEquals(closeableType, directInterfaces.getSingleKnownInterface());
assertTrue(allInterfaces.containsKnownInterface(closeableType));
assertTrue(allInterfaces.containsKnownInterface(autoCloseableType));
assertEquals(2, allInterfaces.size());
} else {
// The interfaces are still present due to the android jar for K, but they are marked
// unknown.
assertTrue(directInterfaces.contains(closeableType).isUnknown());
assertFalse(directInterfaces.containsKnownInterface(closeableType));
assertEquals(1, directInterfaces.size());
// Same for the collection of all interfaces. Since the
assertTrue(allInterfaces.contains(closeableType).isUnknown());
assertTrue(allInterfaces.contains(autoCloseableType).isUnknown());
assertFalse(allInterfaces.containsKnownInterface(closeableType));
assertFalse(allInterfaces.containsKnownInterface(autoCloseableType));
assertEquals(2, allInterfaces.size());
}
}
ClassTypeElement scannerTypeElement = makeTypeElement(appView, scannerType);
{
// Scanner implements Closable and Iterator on all APIs.
InterfaceCollection directInterfaces = scannerTypeElement.getInterfaces();
assertTrue(directInterfaces.containsKnownInterface(closeableType));
assertEquals(2, directInterfaces.size());
// Joining a type of known with a type of unknown should still be unknown.
ClassTypeElement joinLeft1 =
scannerTypeElement.join(zipFileTypeElement, appView).asClassType();
ClassTypeElement joinRight1 =
zipFileTypeElement.join(scannerTypeElement, appView).asClassType();
if (zipFileHasCloseable()) {
assertTrue(joinLeft1.getInterfaces().contains(closeableType).isTrue());
assertTrue(joinRight1.getInterfaces().contains(closeableType).isTrue());
} else {
assertTrue(joinLeft1.getInterfaces().contains(closeableType).isUnknown());
assertTrue(joinRight1.getInterfaces().contains(closeableType).isUnknown());
}
}
// Custom class derived from JarFile and Channel, thus it must implement closable.
ClassTypeElement myJarFileScannerTypeElement = makeTypeElement(appView, myJarFileChannel);
// Joining with Scanner will retain it as always Closeable.
ClassTypeElement joinWithScanner =
myJarFileScannerTypeElement.join(scannerTypeElement, appView).asClassType();
assertTrue(joinWithScanner.getInterfaces().contains(closeableType).isTrue());
// Joining with ZipFile will loose the assurance that it is Closeable.
ClassTypeElement joinWithZipFile =
myJarFileScannerTypeElement.join(zipFileTypeElement, appView).asClassType();
assertTrue(joinWithZipFile.getInterfaces().contains(closeableType).isPossiblyTrue());
assertEquals(
zipFileHasCloseable(), joinWithZipFile.getInterfaces().contains(closeableType).isTrue());
}
private boolean zipFileHasCloseable() {
return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
}
private static ClassTypeElement makeTypeElement(
AppView<AppInfoWithClassHierarchy> appView, DexType type) {
return ClassTypeElement.create(type, Nullability.maybeNull(), appView);
}
static class MyJarFileScanner extends JarFile implements Channel {
public MyJarFileScanner(String name) throws IOException {
super(name);
}
@Override
public boolean isOpen() {
return false;
}
}
static class TestClass {
public static void foo(Object o) throws Exception {
if (o instanceof AutoCloseable) {
System.out.println("Is an AutoCloseable");
((AutoCloseable) o).close();
} else {
System.out.println("Not an AutoCloseable");
}
}
public static void main(String[] args) throws Exception {
foo(new JarFile(args[0]));
}
}
}