blob: 908ce00e29e6511d143d1fde8a2dc0d8edd2e2fe [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.ir.optimize.reflection;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
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 class GetClassTest extends ReflectionOptimizerTestBase {
static class Base {}
static class Sub extends Base {}
@NoHorizontalClassMerging
static class EffectivelyFinal {}
@NoHorizontalClassMerging
static class Reflection {
public Class<?> call() {
return getClass();
}
}
@NoHorizontalClassMerging
static class GetClassTestMain {
@NeverInline
static Class<?> getMainClass(GetClassTestMain instance) {
// Nullable argument. Should not be rewritten to const-class to preserve NPE.
return instance.getClass();
}
@NeverInline
public Class<?> call() {
// Non-null `this` pointer.
return getClass();
}
}
static class Main {
public static void main(String[] args) {
{
Base base = new Base();
// Not applicable in debug mode.
System.out.println(base.getClass());
// Can be rewritten to const-class always.
System.out.println(new Base().getClass());
}
{
Base sub = new Sub();
// Not applicable in debug mode.
System.out.println(sub.getClass());
}
{
Base[] subs = new Sub[1];
// Not applicable in debug mode.
System.out.println(subs.getClass());
}
{
EffectivelyFinal ef = new EffectivelyFinal();
// Not applicable in debug mode.
System.out.println(ef.getClass());
}
try {
// To not be recognized as un-instantiated class.
GetClassTestMain instance = new GetClassTestMain();
System.out.println(instance.call());
System.out.println(GetClassTestMain.getMainClass(instance));
System.out.println(GetClassTestMain.getMainClass(null));
throw new AssertionError("Should preserve NPE.");
} catch (NullPointerException e) {
// Expected
}
{
Reflection r = new Reflection();
// Not applicable in debug mode.
System.out.println(r.getClass());
try {
// Can be rewritten to const-class after inlining.
System.out.println(r.call());
} catch (Throwable e) {
throw new AssertionError("Not expected any exceptions.");
}
}
}
}
private static final String JAVA_OUTPUT =
StringUtils.lines(
ListUtils.map(
ImmutableList.of(
Base.class.getTypeName(),
Base.class.getTypeName(),
Sub.class.getTypeName(),
"[L" + Sub.class.getTypeName() + ";",
EffectivelyFinal.class.getTypeName(),
GetClassTestMain.class.getTypeName(),
GetClassTestMain.class.getTypeName(),
Reflection.class.getTypeName(),
Reflection.class.getTypeName()),
l -> "class " + l));
private static final Class<?> MAIN = Main.class;
@Parameterized.Parameters(name = "{0}, mode:{1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
}
private final TestParameters parameters;
private final CompilationMode mode;
public GetClassTest(TestParameters parameters, CompilationMode mode) {
this.parameters = parameters;
this.mode = mode;
}
@Test
public void testJVM() throws Exception {
assumeTrue(
"Only run JVM reference on CF runtimes",
parameters.isCfRuntime() && mode == CompilationMode.DEBUG);
testForJvm()
.addInnerClasses(GetClassTest.class)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
}
private void test(
CodeInspector codeInspector,
boolean expectCallPresent,
int expectedGetClassCount,
int expectedConstClassCount) {
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject mainMethod = mainClass.mainMethod();
assertThat(mainMethod, isPresent());
assertEquals(expectedGetClassCount, countGetClass(mainMethod));
assertEquals(expectedConstClassCount, countConstClass(mainMethod));
ClassSubject reflectionClass = codeInspector.clazz(Reflection.class);
assertThat(reflectionClass, isPresent());
assertThat(
reflectionClass.uniqueMethodWithName("call"), onlyIf(expectCallPresent, isPresent()));
ClassSubject getterClass = codeInspector.clazz(GetClassTestMain.class);
MethodSubject getMainClass = getterClass.uniqueMethodWithName("getMainClass");
assertThat(getMainClass, isPresent());
// Because of nullable argument, getClass() should remain.
assertEquals(1, countGetClass(getMainClass));
assertEquals(0, countConstClass(getMainClass));
MethodSubject call = getterClass.method("java.lang.Class", "call", ImmutableList.of());
if (!expectCallPresent) {
assertThat(call, not(isPresent()));
} else {
assertThat(call, isPresent());
// Because of local, only R8 release mode can rewrite getClass() to const-class.
assertEquals(1, countGetClass(call));
assertEquals(0, countConstClass(call));
}
}
@Test
public void testD8() throws Exception {
assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
testForD8()
.setMode(mode)
.addInnerClasses(GetClassTest.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT)
.inspect(inspector -> test(inspector, true, 6, 0));
}
@Test
public void testR8() throws Exception {
boolean isRelease = mode == CompilationMode.RELEASE;
boolean expectCallPresent = !isRelease;
int expectedGetClassCount = isRelease ? 0 : 5;
int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1;
testForR8(parameters.getBackend())
.setMode(mode)
.addInnerClasses(GetClassTest.class)
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.addKeepMainRule(MAIN)
.addDontObfuscate()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT)
.inspect(
inspector ->
test(inspector, expectCallPresent, expectedGetClassCount, expectedConstClassCount));
}
}