blob: ba1c5760c548f884ae53ec93e27d1df876219bb2 [file] [log] [blame]
// Copyright (c) 2019, 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.keptgraph;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverPropagateValue;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersBuilder;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class KeptSingletonIsNotCyclicTest extends TestBase {
private static final String EXPECTED = StringUtils.lines("Foo!");
private final TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return TestParametersBuilder.builder().withCfRuntimes().build();
}
public KeptSingletonIsNotCyclicTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testStaticMethod() throws Exception {
test(FooStaticMethod.class, TestStaticMethod.class, null);
}
@Test
public void testStaticField() throws Exception {
test(
FooStaticField.class,
TestStaticField.class,
builder -> builder.enableInliningAnnotations().enableMemberValuePropagationAnnotations());
}
private void test(
Class<?> fooClass, Class<?> testClass, ThrowableConsumer<R8FullTestBuilder> configuration)
throws Exception {
WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
GraphInspector inspector =
testForR8(parameters.getBackend())
.enableNeverClassInliningAnnotations()
.enableGraphInspector(whyAreYouKeepingConsumer)
.addProgramClasses(testClass, fooClass)
.addKeepMainRule(testClass)
.apply(configuration)
.run(parameters.getRuntime(), testClass)
.assertSuccessWithOutput(EXPECTED)
.graphInspector();
ClassReference fooClassRef = Reference.classFromClass(fooClass);
MethodReference fooClInitRef = Reference.classConstructor(fooClassRef);
MethodReference fooInitRef = Reference.methodFromMethod(fooClass.getDeclaredConstructor());
MethodReference testInitRef =
Reference.methodFromMethod(testClass.getDeclaredConstructor());
MethodReference mainMethodRef =
Reference.methodFromMethod(testClass.getDeclaredMethod("main", String[].class));
// Check that whyareyoukeeping does not mention cycles.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
whyAreYouKeepingConsumer.printWhyAreYouKeeping(fooClassRef, new PrintStream(baos));
assertThat(
baos.toString().replace(getClass().getTypeName(), "<test>").toLowerCase(),
not(anyOf(containsString("cyclic"), containsString("cycle"))));
// The only root should be the keep main-method rule.
assertEquals(1, inspector.getRoots().size());
QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
// TestClass.main is kept by the root rule.
QueryNode mainMethod = inspector.method(mainMethodRef).assertNotRenamed().assertKeptBy(root);
// TestClass.<init> is kept by TestClass.main.
QueryNode testInit = inspector.method(testInitRef).assertPresent().assertKeptBy(mainMethod);
// The type Foo is kept by TestClass.<init>
QueryNode fooClassNode = inspector.clazz(fooClassRef).assertRenamed().assertKeptBy(testInit);
// Foo.<clinit> is kept by Foo
QueryNode fooClInit = inspector.method(fooClInitRef).assertPresent().assertKeptBy(fooClassNode);
// The type Foo is also kept by Foo.<clinit>
fooClassNode.assertKeptBy(fooClInit);
// Foo.<init> is kept by Foo.<clinit>
QueryNode fooInit = inspector.method(fooInitRef).assertPresent().assertKeptBy(fooClInit);
}
public static final class FooStaticMethod {
static final FooStaticMethod INSTANCE = new FooStaticMethod();
public static FooStaticMethod getInstance() {
return INSTANCE;
}
private FooStaticMethod() {}
@Override
public String toString() {
return "Foo!";
}
}
@NeverClassInline
public static class TestStaticMethod {
public FooStaticMethod foo;
public TestStaticMethod() {
this.foo = FooStaticMethod.getInstance();
}
public static void main(String[] args) {
System.out.println(new TestStaticMethod().foo.toString());
}
}
public static final class FooStaticField {
static final FooStaticField INSTANCE = new FooStaticField();
private FooStaticField() {}
// Ensure that toString() remains in TestStaticField.main(). Otherwise the expression
// `new TestStaticField().foo.toString()` can be optimized into "Foo!".
@NeverInline
@NeverPropagateValue
@Override
public String toString() {
return "Foo!";
}
}
@NeverClassInline
public static class TestStaticField {
public FooStaticField foo;
public TestStaticField() {
this.foo = FooStaticField.INSTANCE;
}
public static void main(String[] args) {
System.out.println(new TestStaticField().foo.toString());
}
}
}