blob: 7756299e1c76063eb1e62ff04115b37958733cd0 [file] [log] [blame]
// 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.TestParameters;
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 = "{0}, argument removal: {1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
public NonConstructorRelaxationTest(TestParameters parameters, boolean enableArgumentRemoval) {
super(parameters);
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 (parameters.isCfRuntime()) {
// Only run JVM reference on CF runtimes.
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), mainClass)
.assertSuccessWithOutput(expectedOutput);
}
R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()))
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
.addKeepMainRule(mainClass)
.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.
"-checkdiscard class " + A.class.getCanonicalName() + "{",
" *** pBaz();",
" *** pBar();",
" *** pBar1();",
" *** pBlah1();",
"}",
"-checkdiscard class " + B.class.getCanonicalName() + "{",
" *** pBlah1();",
"}",
"-checkdiscard class " + BB.class.getCanonicalName() + "{",
" *** pBlah1();",
"}")
.allowAccessModification()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), 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 (parameters.isCfRuntime()) {
// Only run JVM reference on CF runtimes.
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), mainClass)
.assertSuccessWithOutput(expectedOutput);
}
R8TestRunResult result =
testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()))
.addKeepMainRule(mainClass)
.addOptionsModification(o -> o.enableVerticalClassMerging = enableVerticalClassMerging)
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
.noMinification()
.addKeepRules(
"-checkdiscard class " + Base.class.getCanonicalName() + "{",
" *** p*();",
"}",
"-checkdiscard class " + Sub1.class.getCanonicalName() + "{",
" *** p*();",
"}",
"-checkdiscard class " + Sub2.class.getCanonicalName() + "{",
" *** p*();",
"}")
.allowAccessModification()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), 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")));
}
}
}