blob: c2ec4b7716e5f2b247b4459d8c4da25dba0990cd [file] [log] [blame]
// Copyright (c) 2016, 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.memberrebinding;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.android.tools.r8.utils.dexinspector.DexInspector;
import com.android.tools.r8.utils.dexinspector.FieldAccessInstructionSubject;
import com.android.tools.r8.utils.dexinspector.InstructionSubject;
import com.android.tools.r8.utils.dexinspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.dexinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class MemberRebindingTest {
private static final List<Path> JAR_LIBRARIES = ImmutableList.of(
ToolHelper.getDefaultAndroidJar(),
Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "memberrebindinglib.jar"));
private enum Frontend {
DEX, JAR;
@Override
public String toString() {
return this == DEX ? ".dex" : ".jar";
}
}
private final Frontend kind;
private final Path originalDex;
private final Path programFile;
private final Consumer<DexInspector> inspection;
private final Consumer<DexInspector> originalInspection;
private final int minApiLevel;
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@Rule
public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
public MemberRebindingTest(TestConfiguration configuration) {
this.kind = configuration.kind;
originalDex = configuration.getDexPath();
if (kind == Frontend.DEX) {
this.programFile = originalDex;
} else {
this.programFile = configuration.getJarPath();
}
this.inspection = configuration.processedInspection;
this.originalInspection = configuration.originalInspection;
this.minApiLevel = configuration.getMinApiLevel();
}
@Before
public void runR8() throws Exception {
// Generate R8 processed version without library option.
String out = temp.getRoot().getCanonicalPath();
// NOTE: It is important to turn off inlining to ensure
// dex inspection of invokes is predictable.
R8Command.Builder builder = R8Command.builder()
.setOutput(Paths.get(out), OutputMode.DexIndexed)
.addLibraryFiles(JAR_LIBRARIES)
.setMinApiLevel(minApiLevel);
ToolHelper.getAppBuilder(builder).addProgramFiles(programFile);
ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
}
private static boolean coolInvokes(InstructionSubject instruction) {
if (!instruction.isInvokeVirtual() && !instruction.isInvokeInterface() &&
!instruction.isInvokeStatic()) {
return false;
}
InvokeInstructionSubject invoke = (InvokeInstructionSubject) instruction;
return !invoke.holder().is("java.io.PrintStream");
}
private static void inspectOriginalMain(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
.method(DexInspector.MAIN);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(MemberRebindingTest::coolInvokes);
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClass"));
assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebindinglib.AnIndependentInterface"));
assertTrue(
iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
assertTrue(
iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
assertFalse(iterator.hasNext());
}
private static void inspectMain(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
.method(DexInspector.MAIN);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(MemberRebindingTest::coolInvokes);
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassInMiddleOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding.SuperClassOfAll"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
// For the next three - test that we re-bind to library methods (holder is java.util.ArrayList).
assertTrue(iterator.next().holder().is("java.util.ArrayList"));
assertTrue(iterator.next().holder().is("java.util.ArrayList"));
assertTrue(iterator.next().holder().is("java.util.ArrayList"));
assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClassInTheMiddle"));
assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClassInTheMiddle"));
// For the next three - test that we re-bind to the lowest library class.
assertTrue(iterator.next().holder().is("memberrebindinglib.SubClass"));
assertTrue(iterator.next().holder().is("memberrebindinglib.SubClass"));
assertTrue(iterator.next().holder().is("memberrebindinglib.SubClass"));
// The next one is already precise.
assertTrue(
iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
// Some dispatches on interfaces.
assertTrue(iterator.next().holder().is("memberrebindinglib.AnIndependentInterface"));
assertTrue(iterator.next().holder().is("memberrebindinglib.SubClass"));
assertTrue(iterator.next().holder().is("memberrebindinglib.ImplementedInProgramClass"));
assertFalse(iterator.hasNext());
}
private static void inspectOriginalMain2(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
.method(DexInspector.MAIN);
Iterator<FieldAccessInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isFieldAccess);
// Run through instance put, static put, instance get and instance get.
for (int i = 0; i < 4; i++) {
assertTrue(iterator.next().holder().is("memberrebinding2.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding2.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding2.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding2.subpackage.PublicClass"));
}
assertTrue(iterator.next().holder().is("java.lang.System"));
assertFalse(iterator.hasNext());
}
private static void inspectMain2(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
.method(DexInspector.MAIN);
Iterator<FieldAccessInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isFieldAccess);
// Run through instance put, static put, instance get and instance get.
for (int i = 0; i < 4; i++) {
assertTrue(iterator.next().holder().is("memberrebinding2.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding2.ClassInMiddleOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding2.SuperClassOfAll"));
assertTrue(iterator.next().holder().is("memberrebinding2.subpackage.PublicClass"));
}
assertTrue(iterator.next().holder().is("java.lang.System"));
assertFalse(iterator.hasNext());
}
public static MethodSignature TEST =
new MethodSignature("test", "void", new String[]{});
private static void inspectOriginal3(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
assertTrue(iterator.next().holder().is("memberrebinding3.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding3.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding3.ClassAtBottomOfChain"));
assertFalse(iterator.hasNext());
}
private static void inspect3(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
assertTrue(iterator.next().holder().is("memberrebinding3.ClassAtBottomOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding3.ClassInMiddleOfChain"));
assertTrue(iterator.next().holder().is("memberrebinding3.SuperClassOfAll"));
assertFalse(iterator.hasNext());
}
private static void inspectOriginal4(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
assertTrue(iterator.next().holder().is("memberrebinding4.Memberrebinding$Inner"));
assertTrue(iterator.next().holder().is("memberrebinding4.subpackage.PublicInterface"));
assertFalse(iterator.hasNext());
}
private static void inspect4(DexInspector inspector) {
MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
Iterator<InvokeInstructionSubject> iterator =
main.iterateInstructions(InstructionSubject::isInvoke);
assertTrue(iterator.next().holder().is("memberrebinding4.Memberrebinding$Inner"));
assertTrue(iterator.next().holder().is("memberrebinding4.subpackage.PublicInterface"));
assertFalse(iterator.hasNext());
}
private static class TestConfiguration {
private enum AndroidVersion {
PRE_N,
N
}
final String name;
final Frontend kind;
final AndroidVersion version;
final Consumer<DexInspector> originalInspection;
final Consumer<DexInspector> processedInspection;
private TestConfiguration(String name,
Frontend kind,
AndroidVersion version,
Consumer<DexInspector> originalInspection,
Consumer<DexInspector> processedInspection) {
this.name = name;
this.kind = kind;
this.version = version;
this.originalInspection = originalInspection;
this.processedInspection = processedInspection;
}
public static void add(ImmutableList.Builder<TestConfiguration> builder,
String name,
AndroidVersion version,
Consumer<DexInspector> originalInspection,
Consumer<DexInspector> processedInspection) {
if (version == AndroidVersion.PRE_N) {
builder.add(new TestConfiguration(name, Frontend.DEX, version, originalInspection,
processedInspection));
}
builder.add(new TestConfiguration(name, Frontend.JAR, version, originalInspection,
processedInspection));
}
public Path getDexPath() {
return getBuildPath().resolve(name).resolve("classes.dex");
}
public Path getJarPath() {
return getBuildPath().resolve(name + ".jar");
}
public Path getBuildPath() {
switch (version) {
case PRE_N:
return Paths.get(ToolHelper.EXAMPLES_BUILD_DIR);
case N:
return Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR);
default:
Assert.fail();
return null;
}
}
public int getMinApiLevel() {
switch (version) {
case PRE_N:
return AndroidApiLevel.getDefault().getLevel();
case N:
return AndroidApiLevel.N.getLevel();
default:
Assert.fail();
return -1;
}
}
public String toString() {
return name + " " + kind;
}
}
@Parameters(name = "{0}")
public static Collection<TestConfiguration> data() {
ImmutableList.Builder<TestConfiguration> builder = ImmutableList.builder();
TestConfiguration.add(builder, "memberrebinding", TestConfiguration.AndroidVersion.PRE_N,
MemberRebindingTest::inspectOriginalMain, MemberRebindingTest::inspectMain);
TestConfiguration.add(builder, "memberrebinding2", TestConfiguration.AndroidVersion.PRE_N,
MemberRebindingTest::inspectOriginalMain2, MemberRebindingTest::inspectMain2);
TestConfiguration.add(builder, "memberrebinding3", TestConfiguration.AndroidVersion.PRE_N,
MemberRebindingTest::inspectOriginal3, MemberRebindingTest::inspect3);
TestConfiguration.add(builder, "memberrebinding4", TestConfiguration.AndroidVersion.N,
MemberRebindingTest::inspectOriginal4, MemberRebindingTest::inspect4);
return builder.build();
}
@Test
public void memberRebindingTest() throws IOException, InterruptedException, ExecutionException {
Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
String out = temp.getRoot().getCanonicalPath();
Path processed = Paths.get(out, "classes.dex");
if (kind == Frontend.DEX) {
DexInspector inspector = new DexInspector(originalDex);
originalInspection.accept(inspector);
}
DexInspector inspector = new DexInspector(processed);
inspection.accept(inspector);
// We don't run Art, as the test R8RunExamplesTest already does that.
// ToolHelper.checkArtOutputIdentical(originalDex, processed, mainClass, null);
}
}