blob: b2313a722d5851ad27259a3160fe349db32bb9fe [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.shaking.proxy;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.shaking.proxy.testclasses.BaseInterface;
import com.android.tools.r8.shaking.proxy.testclasses.Main;
import com.android.tools.r8.shaking.proxy.testclasses.SubClass;
import com.android.tools.r8.shaking.proxy.testclasses.SubInterface;
import com.android.tools.r8.shaking.proxy.testclasses.TestClass;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import java.util.List;
import java.util.function.Predicate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ProxiesTest extends TestBase {
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public ProxiesTest(TestParameters parameters) {
this.parameters = parameters;
}
private void runTest(
List<String> additionalKeepRules,
ThrowingConsumer<CodeInspector, RuntimeException> inspection,
String expectedResult)
throws Exception {
testForR8(parameters.getBackend())
.addProgramFiles(ToolHelper.getClassFilesForTestPackage(Main.class.getPackage()))
.addKeepMainRule(Main.class)
.addKeepRules(
"-keep class " + Main.class.getCanonicalName() + " {",
// Keep x, y and z to avoid them being inlined into main.
" private void x(com.android.tools.r8.shaking.proxy.testclasses.BaseInterface);",
" private void y(com.android.tools.r8.shaking.proxy.testclasses.SubInterface);",
" private void z(com.android.tools.r8.shaking.proxy.testclasses.TestClass);",
" private void z(com.android.tools.r8.shaking.proxy.testclasses.SubClass);",
"}")
.addKeepRules(additionalKeepRules)
.addOptionsModification(
o -> {
o.enableDevirtualization = false;
o.enableInliningOfInvokesWithNullableReceivers = false;
// Tests indirectly check if a certain method is inlined or not, where the target
// method has at least 4 instructions.
o.inliningInstructionLimit = 4;
})
.noMinification()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(inspection)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(StringUtils.withNativeLineSeparator(expectedResult));
}
private int countInstructionInX(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForX =
new MethodSignature("x", "void", ImmutableList.of(BaseInterface.class.getCanonicalName()));
MethodSubject method = inspector.clazz(Main.class).method(signatureForX);
assert method instanceof FoundMethodSubject;
FoundMethodSubject foundMethod = (FoundMethodSubject) method;
return (int) Streams.stream(foundMethod.iterateInstructions(invoke)).count();
}
private int countInstructionInY(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForY =
new MethodSignature("y", "void", ImmutableList.of(SubInterface.class.getCanonicalName()));
MethodSubject method = inspector.clazz(Main.class).method(signatureForY);
assert method instanceof FoundMethodSubject;
FoundMethodSubject foundMethod = (FoundMethodSubject) method;
return (int)
Streams.stream(foundMethod.iterateInstructions(invoke))
.filter(
instruction -> {
InvokeInstructionSubject invokeInstruction =
(InvokeInstructionSubject) instruction;
return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
})
.count();
}
private int countInstructionInZ(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(TestClass.class.getCanonicalName()));
MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
assert method instanceof FoundMethodSubject;
FoundMethodSubject foundMethod = (FoundMethodSubject) method;
return (int)
Streams.stream(foundMethod.iterateInstructions(invoke))
.filter(
instruction -> {
InvokeInstructionSubject invokeInstruction =
(InvokeInstructionSubject) instruction;
return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
})
.count();
}
private int countInstructionInZSubClass(
CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(SubClass.class.getCanonicalName()));
MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
assert method instanceof FoundMethodSubject;
FoundMethodSubject foundMethod = (FoundMethodSubject) method;
return (int)
Streams.stream(foundMethod.iterateInstructions(invoke))
.filter(
instruction -> {
InvokeInstructionSubject invokeInstruction =
(InvokeInstructionSubject) instruction;
return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
})
.count();
}
private void noInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is inlined into x, y and z.
assertEquals(1, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
public void testNoInterfaceKept() throws Exception {
runTest(ImmutableList.of(),
this::noInterfaceKept,
"TestClass 1\nTestClass 1\nTestClass 1\nProxy\nEXCEPTION\n");
}
private void baseInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x.
assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
// Indirectly assert that method is inlined into y and z.
assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
public void testBaseInterfaceKept() throws Exception {
runTest(ImmutableList.of(
"-keep interface " + BaseInterface.class.getCanonicalName() + " {",
" <methods>;",
"}"),
this::baseInterfaceKept,
"TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n" +
"TestClass 2\nTestClass 2\nTestClass 2\nProxy\nEXCEPTION\n");
}
private void subInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x or y.
assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
// Indirectly assert that method is inlined into z.
assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
public void testSubInterfaceKept() throws Exception {
runTest(ImmutableList.of(
"-keep interface " + SubInterface.class.getCanonicalName() + " {",
" <methods>;",
"}"),
this::subInterfaceKept,
"TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n" +
"TestClass 2\nTestClass 2\nTestClass 2\nProxy\nProxy\nProxy\n" +
"TestClass 3\nTestClass 3\nTestClass 3\n" +
"TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
}
private void classKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x, y or z.
assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
assertEquals(3, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
assertEquals(3, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
public void testClassKept() throws Exception {
runTest(ImmutableList.of(
"-keep class " + TestClass.class.getCanonicalName() + " {",
" <methods>;",
"}"),
this::classKept,
"TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n" +
"TestClass 2\nTestClass 2\nTestClass 2\nProxy\nProxy\nProxy\n" +
"TestClass 3\nTestClass 3\nTestClass 3\n" +
"TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
}
@Test
public void testSubClassKept() throws Exception {
runTest(ImmutableList.of(
"-keep class " + SubClass.class.getCanonicalName() + " {",
" <methods>;",
"}"),
this::classKept,
"TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n" +
"TestClass 2\nTestClass 2\nTestClass 2\nProxy\nProxy\nProxy\n" +
"TestClass 3\nTestClass 3\nTestClass 3\n" +
"TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
}
}