blob: 2490d41f889996f9b7c63302401a3a757b9a3b09 [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;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class NonVirtualOverrideTest extends TestBase {
private static Class<?> main = NonVirtualOverrideTestClass.class;
private static Class<?> A = NonVirtualOverrideTestClass.A.class;
private static Class<?> B = NonVirtualOverrideTestClass.B.class;
private static Class<?> C = NonVirtualOverrideTestClass.C.class;
private final Backend backend;
private final boolean enableClassInlining;
private final boolean enableVerticalClassMerging;
@Parameterized.Parameters(name = "Backend: {0}, class inlining: {1}, vertical class merging: {2}")
public static Collection<Object[]> data() {
ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
for (Backend backend : Backend.values()) {
for (boolean enableClassInlining : ImmutableList.of(true, false)) {
for (boolean enableVerticalClassMerging : ImmutableList.of(true, false)) {
builder.add(new Object[] {backend, enableClassInlining, enableVerticalClassMerging});
}
}
}
return builder.build();
}
public NonVirtualOverrideTest(
Backend backend, boolean enableClassInlining, boolean enableVerticalClassMerging) {
this.backend = backend;
this.enableClassInlining = enableClassInlining;
this.enableVerticalClassMerging = enableVerticalClassMerging;
}
@Test
public void test() throws Exception {
// Construct B such that it inherits from A and shadows method A.m() with a private method.
JasminBuilder jasminBuilder = new JasminBuilder();
ClassBuilder classBuilder = jasminBuilder.addClass(B.getName(), A.getName());
classBuilder.addDefaultConstructor();
for (String methodName : ImmutableList.of("m1", "m2")) {
classBuilder.addPrivateVirtualMethod(
methodName, ImmutableList.of(), "V", jasminCodeForPrinting("In B." + methodName + "()"));
}
for (String methodName : ImmutableList.of("m3", "m4")) {
classBuilder.addStaticMethod(
methodName, ImmutableList.of(), "V", jasminCodeForPrinting("In B." + methodName + "()"));
}
AndroidApp input =
AndroidApp.builder()
.addProgramFiles(
ToolHelper.getClassFileForTestClass(main),
ToolHelper.getClassFileForTestClass(A),
ToolHelper.getClassFileForTestClass(C))
.addClassProgramData(jasminBuilder.buildClasses())
.build();
// Run the program using java.
String referenceResult;
if (backend == Backend.DEX
&& ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V7_0_0)
&& ToolHelper.getDexVm().getVersion().isAtLeast(Version.V5_1_1)) {
referenceResult =
String.join(
System.lineSeparator(),
"In A.m1()",
"In A.m2()",
"In A.m3()",
"In A.m4()",
"In C.m1()",
"In A.m2()",
"In C.m3()",
"In A.m4()",
"In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
"In A.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
"In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
"In C.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
"In C.m1()",
"In C.m3()",
"");
} else {
Path referenceJar = temp.getRoot().toPath().resolve("input.jar");
ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar);
for (Class<?> clazz : ImmutableList.of(main, A, C)) {
inputConsumer.accept(
ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
null);
}
inputConsumer.accept(
ByteDataView.of(jasminBuilder.buildClasses().get(0)),
DescriptorUtils.javaTypeToDescriptor(B.getName()),
null);
inputConsumer.finished(null);
ProcessResult javaResult = ToolHelper.runJava(referenceJar, main.getName());
assertEquals(javaResult.exitCode, 0);
referenceResult = javaResult.stdout;
}
// Run the program on Art after is has been compiled with R8.
AndroidApp compiled =
compileWithR8(
input,
keepMainProguardConfiguration(main),
options -> {
options.enableClassInlining = enableClassInlining;
options.enableVerticalClassMerging = enableVerticalClassMerging;
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
},
backend);
assertEquals(referenceResult, runOnVM(compiled, main, backend));
// Check that B is present and that it doesn't contain the unused private method m2.
if (!enableClassInlining && !enableVerticalClassMerging) {
CodeInspector inspector = new CodeInspector(compiled);
ClassSubject classSubject = inspector.clazz(B.getName());
assertThat(classSubject, isRenamed());
assertThat(classSubject.method("void", "m1", ImmutableList.of()), isPresent());
assertThat(classSubject.method("void", "m2", ImmutableList.of()), not(isPresent()));
assertThat(classSubject.method("void", "m3", ImmutableList.of()), isPresent());
assertThat(classSubject.method("void", "m4", ImmutableList.of()), not(isPresent()));
}
}
private static String[] jasminCodeForPrinting(String message) {
return new String[] {
".limit stack 2",
".limit locals 1",
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"" + message + "\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
"return"
};
}
}
class NonVirtualOverrideTestClass {
public static void main(String[] args) {
A a = new B();
a.m1();
a.m2();
a.m3();
a.m4();
a = new C();
a.m1();
a.m2();
a.m3();
a.m4();
B b = new B();
try {
b.m1();
} catch (IllegalAccessError exception) {
System.out.println("Caught IllegalAccessError when calling B.m1()");
}
try {
b.m3();
} catch (IncompatibleClassChangeError exception) {
System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
}
try {
b = new C();
b.m1();
} catch (IllegalAccessError exception) {
System.out.println("Caught IllegalAccessError when calling B.m1()");
}
try {
b = new C();
b.m3();
} catch (IncompatibleClassChangeError exception) {
System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
}
C c = new C();
c.m1();
c.m3();
}
static class A {
public void m1() {
System.out.println("In A.m1()");
}
public void m2() {
System.out.println("In A.m2()");
}
public void m3() {
System.out.println("In A.m3()");
}
public void m4() {
System.out.println("In A.m4()");
}
}
static class B extends A {
// Will be made private with Jasmin. This method is targeted and can therefore not be removed.
public void m1() {
System.out.println("In B.m1()");
}
// Will be made private with Jasmin. Ends up being dead code because the method is never called.
public void m2() {
System.out.println("In B.m2()");
}
// Will be made static with Jasmin. This method is targeted and can therefore not be removed.
public void m3() {
System.out.println("In B.m3()");
}
// Will be made static with Jasmin. Ends up being dead code because the method is never called.
public void m4() {
System.out.println("In B.m4()");
}
}
static class C extends B {
public void m1() {
System.out.println("In C.m1()");
}
public void m3() {
System.out.println("In C.m3()");
}
}
}