blob: 6c0bfe7a65f9aaff9adbc344a3441938c3b60ad4 [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.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeInterfaceRange;
import com.android.tools.r8.code.InvokeVirtualRange;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
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.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;
public class ProxiesTest extends TestBase {
private void runTest(List<String> additionalKeepRules, Consumer<DexInspector> inspection,
String expectedResult)
throws Exception {
Class mainClass = Main.class;
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()));
builder.addProguardConfiguration(ImmutableList.of(
"-keep class " + mainClass.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);",
" public static void main(java.lang.String[]);",
"}",
"-dontobfuscate"),
Origin.unknown()
);
builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
inspection.accept(new DexInspector(app));
assertEquals(expectedResult, runOnArt(app, mainClass));
}
private int countInstructionInX(DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForX =
new MethodSignature("x", "void", ImmutableList.of(BaseInterface.class.getCanonicalName()));
DexCode x = inspector.clazz(Main.class).method(signatureForX).getMethod().getCode().asDexCode();
return (int) filterInstructionKind(x, invoke).count();
}
private int countInstructionInY(DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForY =
new MethodSignature("y", "void", ImmutableList.of(SubInterface.class.getCanonicalName()));
DexCode y = inspector.clazz(Main.class).method(signatureForY).getMethod().getCode().asDexCode();
return (int) filterInstructionKind(y, invoke)
.filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
.count();
}
private int countInstructionInZ(DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(TestClass.class.getCanonicalName()));
DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
return (int) filterInstructionKind(z, invoke)
.filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
.count();
}
private int countInstructionInZSubClass(
DexInspector inspector, Class<? extends Instruction> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(SubClass.class.getCanonicalName()));
DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
return (int) filterInstructionKind(z, invoke)
.filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
.count();
}
private void noInterfaceKept(DexInspector inspector) {
// Indirectly assert that method is inlined into x, y and z.
assertEquals(1, countInstructionInX(inspector, InvokeInterfaceRange.class));
assertEquals(1, countInstructionInY(inspector, InvokeInterfaceRange.class));
assertEquals(1, countInstructionInZ(inspector, InvokeVirtualRange.class));
}
@Test
public void testNoInterfaceKept() throws Exception {
runTest(ImmutableList.of(),
this::noInterfaceKept,
"TestClass 1\nTestClass 1\nTestClass 1\nProxy\nEXCEPTION\n");
}
private void baseInterfaceKept(DexInspector inspector) {
// Indirectly assert that method is not inlined into x.
assertEquals(3, countInstructionInX(inspector, InvokeInterfaceRange.class));
// Indirectly assert that method is inlined into y and z.
assertEquals(1, countInstructionInY(inspector, InvokeInterfaceRange.class));
assertEquals(1, countInstructionInZ(inspector, InvokeVirtualRange.class));
assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtualRange.class));
}
@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(DexInspector inspector) {
// Indirectly assert that method is not inlined into x or y.
assertEquals(3, countInstructionInX(inspector, InvokeInterfaceRange.class));
assertEquals(3, countInstructionInY(inspector, InvokeInterfaceRange.class));
// Indirectly assert that method is inlined into z.
assertEquals(1, countInstructionInZ(inspector, InvokeVirtualRange.class));
assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtualRange.class));
}
@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(DexInspector inspector) {
// Indirectly assert that method is not inlined into x, y or z.
assertEquals(3, countInstructionInX(inspector, InvokeInterfaceRange.class));
assertEquals(3, countInstructionInY(inspector, InvokeInterfaceRange.class));
assertEquals(3, countInstructionInZ(inspector, InvokeVirtualRange.class));
assertEquals(3, countInstructionInZSubClass(inspector, InvokeVirtualRange.class));
}
@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");
}
}