blob: 3333f39692cb8e5777e535c591f95972e260869d [file] [log] [blame]
// Copyright (c) 2019, 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 desugaredlib;
import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LATEST;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion.LEGACY;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
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 ProgramRewritingTest extends DesugaredLibraryTestBase {
private static final Class<?> TEST_CLASS = ProgramRewritingTestClass.class;
private final TestParameters parameters;
private final LibraryDesugaringSpecification libraryDesugaringSpecification;
private final CompilationSpecification compilationSpecification;
@Parameters(name = "{0}, spec: {1}, {2}")
public static List<Object[]> data() {
LibraryDesugaringSpecification jdk8CoreLambdaStubs =
new LibraryDesugaringSpecification(
"JDK8_CL",
ImmutableSet.of(
DESUGARED_JDK_8_LIB_JAR,
ToolHelper.getDesugarLibConversions(LEGACY),
ToolHelper.getCoreLambdaStubs()),
JDK8.getSpecification(),
ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.O)),
LibraryDesugaringSpecification.JDK8_DESCRIPTOR,
"");
LibraryDesugaringSpecification jdk11CoreLambdaStubs =
new LibraryDesugaringSpecification(
"JDK11_CL",
ImmutableSet.of(
LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar(),
ToolHelper.getDesugarLibConversions(LATEST),
ToolHelper.getCoreLambdaStubs()),
JDK11.getSpecification(),
ImmutableSet.of(ToolHelper.getAndroidJar(AndroidApiLevel.R)),
LibraryDesugaringSpecification.JDK11_DESCRIPTOR,
"");
return buildParameters(
getTestParameters().withDexRuntimes().withAllApiLevels().build(),
ImmutableList.of(JDK8, JDK11, jdk8CoreLambdaStubs, jdk11CoreLambdaStubs),
DEFAULT_SPECIFICATIONS);
}
public ProgramRewritingTest(
TestParameters parameters,
LibraryDesugaringSpecification libraryDesugaringSpecification,
CompilationSpecification compilationSpecification) {
this.parameters = parameters;
this.libraryDesugaringSpecification = libraryDesugaringSpecification;
this.compilationSpecification = compilationSpecification;
}
@Test
public void testRewriting() throws Throwable {
Box<String> keepRules = new Box<>();
SingleTestRunResult<?> run =
testForDesugaredLibrary(
parameters, libraryDesugaringSpecification, compilationSpecification)
.addInnerClassesAndStrippedOuter(getClass())
.addKeepMainRule(TEST_CLASS)
.compile()
.inspect(this::checkRewrittenInvokes)
.inspectKeepRules(
kr -> {
if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
keepRules.set(String.join("\n", kr));
} else {
assert kr == null || kr.isEmpty();
keepRules.set("");
}
})
.run(parameters.getRuntime(), TEST_CLASS);
assertResultIsCorrect(run.getStdOut(), run.getStdErr(), keepRules.get());
}
private void assertResultIsCorrect(String stdOut, String stdErr, String keepRules) {
if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
if (compilationSpecification.isL8Shrink()) {
assertGeneratedKeepRulesAreCorrect(keepRules);
} else {
// When shrinking the class names are not printed correctly anymore due to minification.
assertLines2By2Correct(stdOut);
}
}
if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
// Flaky: There might be a missing method on lambda deserialization.
assertTrue(
!stdErr.contains("Could not find method")
|| stdErr.contains("Could not find method java.lang.invoke.SerializedLambda"));
} else {
assertFalse(stdErr.contains("Could not find method"));
}
}
private void checkRewrittenInvokes(CodeInspector inspector) {
if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
return;
}
ClassSubject classSubject = inspector.clazz(TEST_CLASS);
assertThat(classSubject, isPresent());
List<InstructionSubject> invokes =
classSubject
.uniqueMethodWithOriginalName("main")
.streamInstructions()
.filter(
instr ->
instr.isInvokeInterface() || instr.isInvokeStatic() || instr.isInvokeVirtual())
.filter(
instr -> {
String holder = instr.getMethod().getHolderType().getTypeName();
return !holder.equals("java.lang.Class")
&& !holder.equals("java.lang.Object")
&& !holder.equals("java.io.PrintStream");
})
.collect(Collectors.toList());
assertEquals(22, invokes.size());
assertInvokeStaticMatching(invokes, 0, "Set$-EL;spliterator");
assertInvokeStaticMatching(invokes, 1, "Collection$-EL;stream");
if (compilationSpecification.isProgramShrink()) {
assertInvokeVirtualMatching(invokes, 2, "HashSet;iterator");
} else {
assertInvokeInterfaceMatching(invokes, 2, "Set;iterator");
}
assertInvokeStaticMatching(invokes, 3, "Collection$-EL;stream");
assertInvokeStaticMatching(invokes, 4, "Set$-EL;spliterator");
assertInvokeInterfaceMatching(invokes, 8, "Iterator;remove");
assertInvokeStaticMatching(invokes, 9, "DesugarArrays;spliterator");
assertInvokeStaticMatching(invokes, 10, "DesugarArrays;spliterator");
assertInvokeStaticMatching(invokes, 11, "DesugarArrays;stream");
assertInvokeStaticMatching(invokes, 12, "DesugarArrays;stream");
assertInvokeStaticMatching(invokes, 13, "Collection$-EL;stream");
assertInvokeStaticMatching(invokes, 14, "IntStream$-CC;range");
assertInvokeStaticMatching(invokes, 16, "Comparator$-CC;comparingInt");
assertInvokeStaticMatching(invokes, 17, "List$-EL;sort");
assertInvokeStaticMatching(invokes, 19, "Comparator$-CC;comparingInt");
assertInvokeStaticMatching(invokes, 20, "List$-EL;sort");
assertInvokeStaticMatching(invokes, 21, "Collection$-EL;stream");
}
private void assertInvokeInterfaceMatching(List<InstructionSubject> invokes, int i, String s) {
assertTrue(invokes.get(i).isInvokeInterface());
assertTrue(invokes.get(i).toString().contains(s));
}
private void assertInvokeStaticMatching(List<InstructionSubject> invokes, int i, String s) {
assertTrue(invokes.get(i).isInvokeStatic());
assertTrue(invokes.get(i).toString().contains(s));
}
private void assertInvokeVirtualMatching(List<InstructionSubject> invokes, int i, String s) {
assertTrue(invokes.get(i).isInvokeVirtual());
assertTrue(invokes.get(i).toString().contains(s));
}
private void assertGeneratedKeepRulesAreCorrect(String keepRules) {
String prefix = libraryDesugaringSpecification.functionPrefix(parameters);
String expectedResult =
StringUtils.lines(
"-keep class j$.util.Collection$-EL {",
" public static j$.util.stream.Stream stream(java.util.Collection);",
"}",
"-keep class j$.util.Comparator$-CC {",
" public static java.util.Comparator comparingInt("
+ prefix
+ ".util.function.ToIntFunction);",
"}",
"-keep class j$.util.DesugarArrays {",
" public static j$.util.Spliterator spliterator(java.lang.Object[]);",
" public static j$.util.Spliterator spliterator(java.lang.Object[], int, int);",
" public static j$.util.stream.Stream stream(java.lang.Object[]);",
" public static j$.util.stream.Stream stream(java.lang.Object[], int, int);",
"}",
"-keep class j$.util.List$-EL {",
" public static void sort(java.util.List, java.util.Comparator);",
"}",
"-keep class j$.util.Set$-EL {",
" public static j$.util.Spliterator spliterator(java.util.Set);",
"}",
"-keep interface j$.util.Spliterator {",
"}");
if (prefix.equals("j$")) {
expectedResult +=
StringUtils.lines(
"-keep interface j$.util.function.ToIntFunction {",
" public int applyAsInt(java.lang.Object);",
"}");
}
expectedResult +=
StringUtils.lines(
"-keep class j$.util.stream.IntStream$-CC {",
" public static j$.util.stream.IntStream range(int, int);",
"}",
"-keep interface j$.util.stream.IntStream {",
"}",
"-keep interface j$.util.stream.Stream {",
"}");
if (prefix.equals("java")) {
expectedResult +=
StringUtils.lines("-keep interface java.util.function.ToIntFunction {", "}");
}
assertEquals(expectedResult.trim(), keepRules.trim());
}
public static class ProgramRewritingTestClass {
// Each print to the console is immediately followed by the expected result so the tests
// can assert the results by checking the lines 2 by 2.
public static void main(String[] args) {
Set<Object> set = new HashSet<>();
List<Object> list = new ArrayList<>();
ArrayList<Object> aList = new ArrayList<>();
Queue<Object> queue = new LinkedList<>();
LinkedHashSet<Object> lhs = new LinkedHashSet<>();
// Following should be rewritten to invokeStatic to the dispatch class.
System.out.println(set.spliterator().getClass().getName());
System.out.println("j$.util.Spliterators$IteratorSpliterator");
// Following should be rewritten to invokeStatic to Collection dispatch class.
System.out.println(set.stream().getClass().getName());
System.out.println("j$.util.stream.ReferencePipeline$Head");
// Following should not be rewritten.
System.out.println(set.iterator().getClass().getName());
System.out.println("java.util.HashMap$KeyIterator");
// Following should be rewritten to invokeStatic to Collection dispatch class.
System.out.println(queue.stream().getClass().getName());
System.out.println("j$.util.stream.ReferencePipeline$Head");
// Following should be rewritten as retarget core lib member.
System.out.println(lhs.spliterator().getClass().getName());
System.out.println("j$.util.Spliterators$IteratorSpliterator");
// Remove follows the don't rewrite rule.
list.add(new Object());
Iterator iterator = list.iterator();
iterator.next();
iterator.remove();
// Static methods (same name, different signatures).
System.out.println(Arrays.spliterator(new Object[] {new Object()}).getClass().getName());
System.out.println("j$.util.Spliterators$ArraySpliterator");
System.out.println(
Arrays.spliterator(new Object[] {new Object()}, 0, 0).getClass().getName());
System.out.println("j$.util.Spliterators$ArraySpliterator");
System.out.println(Arrays.stream(new Object[] {new Object()}).getClass().getName());
System.out.println("j$.util.stream.ReferencePipeline$Head");
System.out.println(Arrays.stream(new Object[] {new Object()}, 0, 0).getClass().getName());
System.out.println("j$.util.stream.ReferencePipeline$Head");
// Following should be rewritten to invokeStatic to dispatch class.
System.out.println(list.stream().getClass().getName());
System.out.println("j$.util.stream.ReferencePipeline$Head");
// Following should call companion method (desugared library class).
System.out.println(IntStream.range(0, 5).getClass().getName());
System.out.println("j$.util.stream.IntPipeline$Head");
// Following should call List dispatch (sort), rewritten from invoke interface.
// Comparator.comparingInt should call companion method (desugared library class).
Collections.addAll(list, new Object(), new Object());
list.sort(Comparator.comparingInt(Object::hashCode));
// Following should call List dispatch (sort), rewritten from invoke virtual.
// Comparator.comparingInt should call companion method (desugared library class).
Collections.addAll(aList, new Object(), new Object());
aList.sort(Comparator.comparingInt(Object::hashCode));
// Following should be rewritten to invokeStatic to Collection dispatch class.
System.out.println(list.stream().getClass().getName());
System.out.println("j$.util.stream.ReferencePipeline$Head");
// Following should call companion method (desugared library class) [Java 9].
// System.out.println(Stream.iterate(0,x->x<10,x->x+1).getClass().getName());
}
}
}