blob: af6ca9e61df6aa003ca523f2ef21577522dfd63e [file] [log] [blame]
// Copyright (c) 2020, 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.desugar.desugaredlibrary;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.AbstractSequentialList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.UnaryOperator;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class DesugaredGenericSignatureTest extends DesugaredLibraryTestBase {
private final TestParameters parameters;
private final boolean shrinkDesugaredLibrary;
@Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
BooleanUtils.values());
}
public DesugaredGenericSignatureTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
this.parameters = parameters;
this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
}
@Test
public void testD8() throws Exception {
Assume.assumeTrue(parameters.getRuntime().isDex());
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForD8()
.addInnerClasses(DesugaredGenericSignatureTest.class)
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.setIncludeClassesChecksum(true)
.compile()
.inspect(this::checkRewrittenSignature)
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(expected(parameters, false));
}
@Test
public void testD8Cf2Cf() throws Exception {
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
Path jar =
testForD8(Backend.CF)
.addInnerClasses(DesugaredGenericSignatureTest.class)
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.setIncludeClassesChecksum(true)
.setMinApi(parameters.getApiLevel())
.allowStdoutMessages()
.compile()
.writeToZip();
String desugaredLibraryKeepRules = "";
if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
// Collection keep rules is only implemented in the DEX writer.
assertEquals(0, keepRuleConsumer.get().length());
desugaredLibraryKeepRules = "-keep class * { *; }";
}
if (parameters.getRuntime().isDex()) {
testForD8()
.addProgramFiles(jar)
.setMinApi(parameters.getApiLevel())
.disableDesugaring()
.allowStdoutMessages()
.compile()
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
desugaredLibraryKeepRules,
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(expected(parameters, true));
} else {
testForJvm()
.addProgramFiles(jar)
.addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(expected(parameters, true));
}
}
@Test
public void testR8() throws Exception {
Assume.assumeTrue(parameters.getRuntime().isDex());
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForR8(parameters.getBackend())
.addInnerClasses(DesugaredGenericSignatureTest.class)
.addKeepMainRule(Main.class)
.addKeepAllClassesRuleWithAllowObfuscation()
.addKeepAttributes(
ProguardKeepAttributes.SIGNATURE,
ProguardKeepAttributes.INNER_CLASSES,
ProguardKeepAttributes.ENCLOSING_METHOD)
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.inspect(this::checkRewrittenSignature)
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(expected(parameters, false));
}
private void checkRewrittenSignature(CodeInspector inspector) {
if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
return;
}
ClassSubject javaTimeBox = inspector.clazz(JavaTimeDateBox.class);
assertThat(javaTimeBox, isPresent());
ClassSubject box = inspector.clazz(Box.class);
assertThat(box, isPresent());
String finalBoxDescriptor = box.getFinalDescriptor();
assertEquals(
"Ljava/lang/Object;"
+ finalBoxDescriptor.substring(0, finalBoxDescriptor.length() - 1)
+ "<Lj$/time/LocalDate;>;",
javaTimeBox.getFinalSignatureAttribute());
}
public interface Box<T> {
T addOne(T t);
}
public static class JavaTimeDateBox implements Box<java.time.LocalDate> {
@Override
@NeverInline
public LocalDate addOne(LocalDate localDate) {
return localDate.plusDays(1);
}
}
private static String expected(
TestParameters parameters, boolean genericSignaturesOnEmulatedInterfaces) {
final String EXPECTED = StringUtils.lines("Box", "1970", "1", "2");
final String STRING_KEY_HASH_MAP_EXPECTED =
StringUtils.lines(
"StringKeyHashMap", "1", "j$.util.Map<java.lang.String, T>", "2", "true", "true");
final String SAME_KEY_AND_VALUE_TYPE_HASH_MAP_EXPECTED =
StringUtils.lines(
"SameKeyAndValueTypeHashMap", "1", "j$.util.Map<T, T>", "2", "true", "true");
final String TRANSFORMING_SEQUENTIAL_LIST_EXPECTED =
StringUtils.lines("TransformingSequentialList", "2", "j$.util.List<T>");
final String EXPECTED_WITH_EMULATED_INTERFACE =
STRING_KEY_HASH_MAP_EXPECTED
+ SAME_KEY_AND_VALUE_TYPE_HASH_MAP_EXPECTED
+ TRANSFORMING_SEQUENTIAL_LIST_EXPECTED;
final String EXPECTED_WITHOUT_EMULATED_INTERFACE_ART_BEFORE_O =
StringUtils.lines(
"StringKeyHashMap",
"1",
"interface j$.util.Map",
"SameKeyAndValueTypeHashMap",
"1",
"interface j$.util.Map",
"TransformingSequentialList",
"2",
"interface j$.util.List");
final String EXPECTED_WITHOUT_EMULATED_INTERFACE_JVM_AND_ART_FROM_O =
StringUtils.lines(
"StringKeyHashMap",
"0",
"SameKeyAndValueTypeHashMap",
"0",
"TransformingSequentialList",
"1");
return EXPECTED
+ (genericSignaturesOnEmulatedInterfaces
&& !parameters
.getApiLevel()
.isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())
? EXPECTED_WITH_EMULATED_INTERFACE
: (parameters.isDexRuntime()
&& (parameters
.getRuntime()
.asDex()
.getMinApiLevel()
.isLessThan(AndroidApiLevel.N)
|| parameters
.getApiLevel()
.isLessThan(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())))
? EXPECTED_WITHOUT_EMULATED_INTERFACE_ART_BEFORE_O
: EXPECTED_WITHOUT_EMULATED_INTERFACE_JVM_AND_ART_FROM_O);
}
public static class Main {
public static Box<java.time.LocalDate> bar() {
return new JavaTimeDateBox();
}
public static void main(String[] args) {
testBox();
testEmulatedInterfaceGenericSignatureStringKeyHashMap();
testEmulatedInterfaceGenericSignatureSameKeyAndValueTypeHashMap();
testEmulatedInterfaceGenericSignatureTransformingSequentialList();
}
public static void testBox() {
System.out.println("Box");
LocalDate localDate = bar().addOne(LocalDate.of(1970, 1, 1));
System.out.println(localDate.getYear());
System.out.println(localDate.getMonthValue());
System.out.println(localDate.getDayOfMonth());
}
public static void testEmulatedInterfaceGenericSignatureStringKeyHashMap() {
System.out.println("StringKeyHashMap");
Class<?> clazz = StringKeyHashMap.class;
System.out.println(clazz.getGenericInterfaces().length);
if (clazz.getGenericInterfaces().length == 0) {
return;
}
Type genericInterface = clazz.getGenericInterfaces()[0];
System.out.println(genericInterface);
if (genericInterface instanceof ParameterizedType) {
// The j$.util.Map emulated interface has the generic type arguments <String, T>.
Type[] actualTypeArguments =
((ParameterizedType) genericInterface).getActualTypeArguments();
System.out.println(actualTypeArguments.length);
System.out.println(actualTypeArguments[0].equals(String.class));
System.out.println(actualTypeArguments[1].equals(clazz.getTypeParameters()[0]));
}
}
public static void testEmulatedInterfaceGenericSignatureSameKeyAndValueTypeHashMap() {
System.out.println("SameKeyAndValueTypeHashMap");
Class<?> clazz = SameKeyAndValueTypeHashMap.class;
System.out.println(clazz.getGenericInterfaces().length);
if (clazz.getGenericInterfaces().length == 0) {
return;
}
Type genericInterface = clazz.getGenericInterfaces()[0];
System.out.println(genericInterface);
if (genericInterface instanceof ParameterizedType) {
// The j$.util.Map emulated interface has the generic type arguments <T, T>.
Type[] actualTypeArguments =
((ParameterizedType) genericInterface).getActualTypeArguments();
System.out.println(actualTypeArguments.length);
System.out.println(actualTypeArguments[0].equals(clazz.getTypeParameters()[0]));
System.out.println(actualTypeArguments[1].equals(clazz.getTypeParameters()[0]));
}
}
public static void testEmulatedInterfaceGenericSignatureTransformingSequentialList() {
System.out.println("TransformingSequentialList");
Class<?> clazz = TransformingSequentialList.class;
System.out.println(clazz.getGenericInterfaces().length);
if (clazz.getGenericInterfaces().length == 1) {
return;
}
// j$.util.List emulated interface has the generic type argument <T>.
System.out.println(clazz.getGenericInterfaces()[1]);
}
}
// LinkedHashMap implements Map.
abstract static class StringKeyHashMap<T> extends LinkedHashMap<String, T> {
// Need at least one overridden default method for emulated dispatch.
@Override
public T getOrDefault(Object key, T defaultValue) {
return super.getOrDefault(key, defaultValue);
}
}
// LinkedHashMap implements Map.
abstract static class SameKeyAndValueTypeHashMap<T> extends LinkedHashMap<T, T> {
// Need at least one overridden default method for emulated dispatch.
@Override
public T getOrDefault(Object key, T defaultValue) {
return super.getOrDefault(key, defaultValue);
}
}
// AbstractSequentialList implements List further up the hierarchy.
abstract static class TransformingSequentialList<F, T> extends AbstractSequentialList<T>
implements Serializable {
// Need at least one overridden default method for emulated dispatch.
@Override
public void replaceAll(UnaryOperator<T> operator) {}
}
}