| // 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 com.android.tools.r8.desugar.desugaredlibrary; |
| |
| 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.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.nio.file.Paths; |
| import java.util.List; |
| import java.util.stream.Collectors; |
| 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 String TEST_CLASS = "stream.ProgramRewritingTestClass"; |
| |
| 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) |
| .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar")) |
| .addKeepMainRule(TEST_CLASS) |
| .applyIf( |
| compilationSpecification.isProgramShrink(), |
| b -> |
| b.addOptionsModification( |
| options -> { |
| // TODO(b/140233505): Allow devirtualization once fixed. |
| options.enableDevirtualization = false; |
| })) |
| .compile() |
| .inspect(this::checkRewrittenInvokes) |
| .inspectKeepRules( |
| kr -> { |
| if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) { |
| keepRules.set(String.join("\n", kr)); |
| } else { |
| assert kr == null; |
| 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()) |
| .collect(Collectors.toList()); |
| assertInvokeStaticMatching(invokes, 0, "Set$-EL;spliterator"); |
| assertInvokeStaticMatching(invokes, 1, "Collection$-EL;stream"); |
| 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"); |
| assertEquals(22, invokes.size()); |
| } |
| |
| 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 assertGeneratedKeepRulesAreCorrect(String keepRules) { |
| |
| String prefix = libraryDesugaringSpecification.functionPrefix(parameters); |
| String expectedResult = |
| StringUtils.lines( |
| "-keep class j$.util.Collection$-EL {", |
| " j$.util.stream.Stream stream(java.util.Collection);", |
| "}", |
| "-keep class j$.util.Comparator$-CC {", |
| " java.util.Comparator comparingInt(" + prefix + ".util.function.ToIntFunction);", |
| "}", |
| "-keep class j$.util.DesugarArrays {", |
| " j$.util.Spliterator spliterator(java.lang.Object[]);", |
| " j$.util.Spliterator spliterator(java.lang.Object[], int, int);", |
| " j$.util.stream.Stream stream(java.lang.Object[]);", |
| " j$.util.stream.Stream stream(java.lang.Object[], int, int);", |
| "}", |
| "-keep class j$.util.List$-EL {", |
| " void sort(java.util.List, java.util.Comparator);", |
| "}", |
| "-keep class j$.util.Set$-EL {", |
| " j$.util.Spliterator spliterator(java.util.Set);", |
| "}", |
| "-keep class j$.util.Spliterator", |
| "-keep class " + prefix + ".util.function.ToIntFunction { *; }", |
| "-keep class j$.util.stream.IntStream$-CC {", |
| " j$.util.stream.IntStream range(int, int);", |
| "}", |
| "-keep class j$.util.stream.IntStream", |
| "-keep class j$.util.stream.Stream"); |
| assertEquals(expectedResult.trim(), keepRules.trim()); |
| } |
| } |