| // 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()); |
| } |
| } |
| } |