| // 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.accessrelaxation; | |
| import static org.junit.Assert.assertEquals; | |
| import com.android.tools.r8.R8TestRunResult; | |
| import com.android.tools.r8.ToolHelper; | |
| import com.android.tools.r8.accessrelaxation.privateinstance.Base; | |
| import com.android.tools.r8.accessrelaxation.privateinstance.Sub1; | |
| import com.android.tools.r8.accessrelaxation.privateinstance.Sub2; | |
| import com.android.tools.r8.accessrelaxation.privateinstance.TestMain; | |
| import com.android.tools.r8.accessrelaxation.privatestatic.A; | |
| import com.android.tools.r8.accessrelaxation.privatestatic.B; | |
| import com.android.tools.r8.accessrelaxation.privatestatic.BB; | |
| import com.android.tools.r8.accessrelaxation.privatestatic.C; | |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; | |
| import com.android.tools.r8.utils.BooleanUtils; | |
| import com.android.tools.r8.utils.StringUtils; | |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; | |
| import com.google.common.collect.ImmutableList; | |
| import java.util.List; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.Parameterized; | |
| @RunWith(Parameterized.class) | |
| public final class NonConstructorRelaxationTest extends AccessRelaxationTestBase { | |
| private static final String STRING = "java.lang.String"; | |
| private boolean enableArgumentRemoval; | |
| @Parameterized.Parameters(name = "Backend: {0}, argument removal: {1}") | |
| public static List<Object[]> data() { | |
| return buildParameters(Backend.values(), BooleanUtils.values()); | |
| } | |
| public NonConstructorRelaxationTest(Backend backend, boolean enableArgumentRemoval) { | |
| super(backend); | |
| this.enableArgumentRemoval = enableArgumentRemoval; | |
| } | |
| @Test | |
| public void testStaticMethodRelaxation() throws Exception { | |
| String expectedOutput = | |
| StringUtils.lines( | |
| "A::baz()", | |
| "A::bar()", | |
| "A::bar(int)", | |
| "A::blah(int)", | |
| "B::blah(int)", | |
| "BB::blah(int)", | |
| "A::foo()A::baz()A::bar()A::bar(int)", | |
| "B::bar() >> java.lang.IllegalAccessError", | |
| "java.lang.IllegalAccessError", | |
| "A::foo()A::baz()A::bar()A::bar(int)", | |
| "B::blah(int)", | |
| "A::foo()A::baz()A::bar()A::bar(int)", | |
| "B::bar() >> java.lang.IllegalAccessError", | |
| "C::bar(int)java.lang.IllegalAccessErrorB::bar() >> " | |
| + "java.lang.IllegalAccessErrorB::bar() >> java.lang.IllegalAccessError", | |
| "B::foo()A::foo()A::baz()A::bar()A::bar(int)", | |
| "C::blah(int)"); | |
| Class<?> mainClass = C.class; | |
| if (backend == Backend.CF) { | |
| // Only run JVM reference once (for CF backend) | |
| testForJvm().addTestClasspath().run(mainClass).assertSuccessWithOutput(expectedOutput); | |
| } | |
| R8TestRunResult result = | |
| testForR8(backend) | |
| .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())) | |
| .enableInliningAnnotations() | |
| .enableMemberValuePropagationAnnotations() | |
| .addOptionsModification(o -> o.enableArgumentRemoval = enableArgumentRemoval) | |
| .noMinification() | |
| .addKeepRules( | |
| // Note: we use '-checkdiscard' to indirectly check that the access relaxation is | |
| // done which leads to inlining of all pB*** methods so they are removed. Without | |
| // access relaxation inlining is not performed and method are kept. | |
| "-keep class " + mainClass.getCanonicalName() + "{", | |
| " public static void main(java.lang.String[]);", | |
| "}", | |
| "", | |
| "-checkdiscard class " + A.class.getCanonicalName() + "{", | |
| " *** pBaz();", | |
| " *** pBar();", | |
| " *** pBar1();", | |
| " *** pBlah1();", | |
| "}", | |
| "", | |
| "-checkdiscard class " + B.class.getCanonicalName() + "{", | |
| " *** pBlah1();", | |
| "}", | |
| "", | |
| "-checkdiscard class " + BB.class.getCanonicalName() + "{", | |
| " *** pBlah1();", | |
| "}", | |
| "", | |
| "-allowaccessmodification") | |
| .run(mainClass); | |
| assertEquals( | |
| expectedOutput, | |
| result | |
| .getStdOut() | |
| .replace("java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError")); | |
| CodeInspector inspector = result.inspector(); | |
| assertPublic(inspector, A.class, new MethodSignature("baz", STRING, ImmutableList.of())); | |
| assertPublic(inspector, A.class, new MethodSignature("bar", STRING, ImmutableList.of())); | |
| assertPublic(inspector, A.class, new MethodSignature("bar", STRING, ImmutableList.of("int"))); | |
| MethodSignature blahMethodSignature = | |
| new MethodSignature( | |
| "blah", STRING, enableArgumentRemoval ? ImmutableList.of() : ImmutableList.of("int")); | |
| assertPublic(inspector, A.class, blahMethodSignature); | |
| assertPublic(inspector, B.class, blahMethodSignature); | |
| assertPublic(inspector, BB.class, blahMethodSignature); | |
| } | |
| @Test | |
| public void testInstanceMethodRelaxationWithVerticalClassMerging() throws Exception { | |
| testInstanceMethodRelaxation(true); | |
| } | |
| @Test | |
| public void testInstanceMethodRelaxationWithoutVerticalClassMerging() throws Exception { | |
| testInstanceMethodRelaxation(false); | |
| } | |
| private void testInstanceMethodRelaxation(boolean enableVerticalClassMerging) throws Exception { | |
| String expectedOutput = | |
| StringUtils.lines( | |
| "Base::foo()", | |
| "Base::foo1()", | |
| "Base::foo2()", | |
| "Sub1::foo1()", | |
| "Itf1::foo1(0) >> Sub1::foo1()", | |
| "Sub1::bar1(0)", | |
| "Sub2::foo2()", | |
| "Itf2::foo2(0) >> Sub2::foo2()", | |
| "Sub2::bar2(0)"); | |
| Class<?> mainClass = TestMain.class; | |
| if (backend == Backend.CF) { | |
| // Only run JVM reference once (for CF backend) | |
| testForJvm().addTestClasspath().run(mainClass).assertSuccessWithOutput(expectedOutput); | |
| } | |
| R8TestRunResult result = | |
| testForR8(backend) | |
| .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage())) | |
| .addOptionsModification(o -> o.enableVerticalClassMerging = enableVerticalClassMerging) | |
| .enableInliningAnnotations() | |
| .noMinification() | |
| .addKeepRules( | |
| "-keep class " + mainClass.getCanonicalName() + "{", | |
| " public static void main(java.lang.String[]);", | |
| "}", | |
| "", | |
| "-checkdiscard class " + Base.class.getCanonicalName() + "{", | |
| " *** p*();", | |
| "}", | |
| "", | |
| "-checkdiscard class " + Sub1.class.getCanonicalName() + "{", | |
| " *** p*();", | |
| "}", | |
| "", | |
| "-checkdiscard class " + Sub2.class.getCanonicalName() + "{", | |
| " *** p*();", | |
| "}", | |
| "", | |
| "-allowaccessmodification" | |
| ) | |
| .run(mainClass); | |
| assertEquals( | |
| expectedOutput, | |
| result | |
| .getStdOut() | |
| .replace("java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError")); | |
| // When vertical class merging is enabled, Itf1 is merged into Sub1 and Itf2 is merged into | |
| // Sub2, and as a result of these merges, neither Sub1 nor Sub2 end up in the output because of | |
| // inlining. | |
| CodeInspector codeInspector = result.inspector(); | |
| assertPublic(codeInspector, Base.class, new MethodSignature("foo", STRING, ImmutableList.of())); | |
| // Base#foo?() can't be publicized due to Itf<1>#foo<1>(). | |
| assertNotPublic( | |
| codeInspector, Base.class, new MethodSignature("foo1", STRING, ImmutableList.of())); | |
| assertNotPublic( | |
| codeInspector, Base.class, new MethodSignature("foo2", STRING, ImmutableList.of())); | |
| if (!enableVerticalClassMerging) { | |
| // Sub?#bar1(int) can be publicized as they don't bother each other. | |
| assertPublic( | |
| codeInspector, Sub1.class, new MethodSignature("bar1", STRING, ImmutableList.of("int"))); | |
| assertPublic( | |
| codeInspector, Sub2.class, new MethodSignature("bar1", STRING, ImmutableList.of("int"))); | |
| // Sub2#bar2(int) is unique throughout the hierarchy, hence publicized. | |
| assertPublic( | |
| codeInspector, Sub2.class, new MethodSignature("bar2", STRING, ImmutableList.of("int"))); | |
| } | |
| } | |
| } |