| // Copyright (c) 2018, 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.shaking; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.ByteDataView; |
| import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer; |
| import com.android.tools.r8.R8TestCompileResult; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.DexVm.Version; |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.ir.optimize.Inliner.Reason; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import java.nio.file.Path; |
| import java.util.Collection; |
| import java.util.Objects; |
| import java.util.function.Function; |
| import org.junit.ClassRule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class NonVirtualOverrideTest extends TestBase { |
| |
| private final TestParameters parameters; |
| private final boolean enableClassInlining; |
| private final boolean enableVerticalClassMerging; |
| |
| static class Dimensions { |
| |
| private final Backend backend; |
| private final boolean enableClassInlining; |
| private final boolean enableVerticalClassMerging; |
| |
| public Dimensions( |
| Backend backend, boolean enableClassInlining, boolean enableVerticalClassMerging) { |
| this.backend = backend; |
| this.enableClassInlining = enableClassInlining; |
| this.enableVerticalClassMerging = enableVerticalClassMerging; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(backend, enableClassInlining, enableVerticalClassMerging); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof Dimensions)) { |
| return false; |
| } |
| Dimensions other = (Dimensions) o; |
| return this.backend == other.backend |
| && this.enableClassInlining == other.enableClassInlining |
| && this.enableVerticalClassMerging == other.enableVerticalClassMerging; |
| } |
| } |
| |
| @Parameterized.Parameters(name = "Backend: {0}, class inlining: {1}, vertical class merging: {2}") |
| public static Collection<Object[]> data() { |
| return buildParameters( |
| getTestParameters().withAllRuntimes().build(), |
| BooleanUtils.values(), |
| BooleanUtils.values()); |
| } |
| |
| public NonVirtualOverrideTest( |
| TestParameters parameters, boolean enableClassInlining, boolean enableVerticalClassMerging) { |
| this.parameters = parameters; |
| this.enableClassInlining = enableClassInlining; |
| this.enableVerticalClassMerging = enableVerticalClassMerging; |
| } |
| |
| @ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest(); |
| |
| private static Function<Boolean, String> expectedResults = |
| memoizeFunction(NonVirtualOverrideTest::getExpectedResult); |
| |
| private static Function<Dimensions, R8TestCompileResult> compilationResults = |
| memoizeFunction(NonVirtualOverrideTest::compile); |
| |
| public static String getExpectedResult(boolean isOldVm) throws Exception { |
| if (isOldVm) { |
| return String.join( |
| System.lineSeparator(), |
| "In A.m1()", |
| "In A.m2()", |
| "In A.m3()", |
| "In A.m4()", |
| "In C.m1()", |
| "In A.m2()", |
| "In C.m3()", |
| "In A.m4()", |
| "In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1() |
| "In A.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3() |
| "In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1() |
| "In C.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3() |
| "In C.m1()", |
| "In C.m3()", |
| ""); |
| } else { |
| Path referenceJar = staticTemp.getRoot().toPath().resolve("input.jar"); |
| ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar); |
| inputConsumer.accept( |
| ByteDataView.of(NonVirtualOverrideTestClassDump.dump()), |
| DescriptorUtils.javaTypeToDescriptor(NonVirtualOverrideTestClass.class.getName()), |
| null); |
| inputConsumer.accept( |
| ByteDataView.of(ADump.dump()), |
| DescriptorUtils.javaTypeToDescriptor(A.class.getName()), |
| null); |
| inputConsumer.accept( |
| ByteDataView.of(BDump.dump()), |
| DescriptorUtils.javaTypeToDescriptor(B.class.getName()), |
| null); |
| inputConsumer.accept( |
| ByteDataView.of(CDump.dump()), |
| DescriptorUtils.javaTypeToDescriptor(C.class.getName()), |
| null); |
| inputConsumer.finished(null); |
| |
| ProcessResult javaResult = |
| ToolHelper.runJava(referenceJar, NonVirtualOverrideTestClass.class.getName()); |
| assertEquals(javaResult.exitCode, 0); |
| return javaResult.stdout; |
| } |
| } |
| |
| public static boolean isDexVmBetween5_1_1and7_0_0(TestParameters parameters) { |
| if (!parameters.isDexRuntime()) { |
| return false; |
| } |
| Version version = parameters.getRuntime().asDex().getVm().getVersion(); |
| return version.isOlderThanOrEqual(Version.V7_0_0) && version.isAtLeast(Version.V5_1_1); |
| } |
| |
| public static R8TestCompileResult compile(Dimensions dimensions) throws Exception { |
| return testForR8(staticTemp, dimensions.backend) |
| .addProgramClassFileData( |
| NonVirtualOverrideTestClassDump.dump(), ADump.dump(), BDump.dump(), CDump.dump()) |
| .addKeepMainRule(NonVirtualOverrideTestClass.class) |
| .addOptionsModification( |
| options -> { |
| options.enableClassInlining = dimensions.enableClassInlining; |
| options.enableVerticalClassMerging = dimensions.enableVerticalClassMerging; |
| options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE); |
| }) |
| .setMinApi(AndroidApiLevel.B) |
| .compile(); |
| } |
| |
| @Test |
| public void test() throws Exception { |
| // Run the program on Art after is has been compiled with R8. |
| String referenceResult = expectedResults.apply(isDexVmBetween5_1_1and7_0_0(parameters)); |
| R8TestCompileResult compiled = |
| compilationResults.apply( |
| new Dimensions( |
| parameters.getBackend(), enableClassInlining, enableVerticalClassMerging)); |
| compiled |
| .run(parameters.getRuntime(), NonVirtualOverrideTestClass.class) |
| .assertSuccessWithOutput(referenceResult); |
| |
| // Check that B is present and that it doesn't contain the unused private method m2. |
| if (!enableClassInlining && !enableVerticalClassMerging) { |
| CodeInspector inspector = compiled.inspector(); |
| ClassSubject classSubject = inspector.clazz(B.class.getName()); |
| assertThat(classSubject, isRenamed()); |
| assertThat(classSubject.method("void", "m1", ImmutableList.of()), isPresent()); |
| assertThat(classSubject.method("void", "m2", ImmutableList.of()), not(isPresent())); |
| assertThat(classSubject.method("void", "m3", ImmutableList.of()), isPresent()); |
| assertThat(classSubject.method("void", "m4", ImmutableList.of()), not(isPresent())); |
| } |
| } |
| } |