Allow referencing graph nodes that are removed during tree shaking.
R=ricow
Bug: 120959039
Change-Id: I667f1627d5897562554e255210220b89c4bf1b3b
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 673314d..adc6979 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2832,23 +2832,27 @@
}
ClassGraphNode getClassGraphNode(DexType type) {
- return classNodes.computeIfAbsent(type,
- t -> new ClassGraphNode(
- appInfo.definitionFor(type).isLibraryClass(),
- Reference.classFromDescriptor(t.toDescriptorString())));
+ return classNodes.computeIfAbsent(
+ type,
+ t -> {
+ DexClass definition = appInfo.definitionFor(t);
+ return new ClassGraphNode(
+ definition != null && definition.isLibraryClass(),
+ Reference.classFromDescriptor(t.toDescriptorString()));
+ });
}
MethodGraphNode getMethodGraphNode(DexMethod context) {
return methodNodes.computeIfAbsent(
context,
m -> {
- boolean isLibraryNode = appInfo.definitionFor(context.holder).isLibraryClass();
+ DexClass holderDefinition = appInfo.definitionFor(context.holder);
Builder<TypeReference> builder = ImmutableList.builder();
for (DexType param : m.proto.parameters.values) {
builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
}
return new MethodGraphNode(
- isLibraryNode,
+ holderDefinition != null && holderDefinition.isLibraryClass(),
Reference.method(
Reference.classFromDescriptor(m.holder.toDescriptorString()),
m.name.toString(),
@@ -2862,13 +2866,15 @@
FieldGraphNode getFieldGraphNode(DexField context) {
return fieldNodes.computeIfAbsent(
context,
- f ->
- new FieldGraphNode(
- appInfo.definitionFor(context.getHolder()).isLibraryClass(),
- Reference.field(
- Reference.classFromDescriptor(f.getHolder().toDescriptorString()),
- f.name.toString(),
- Reference.typeFromDescriptor(f.type.toDescriptorString()))));
+ f -> {
+ DexClass holderDefinition = appInfo.definitionFor(context.getHolder());
+ return new FieldGraphNode(
+ holderDefinition != null && holderDefinition.isLibraryClass(),
+ Reference.field(
+ Reference.classFromDescriptor(f.getHolder().toDescriptorString()),
+ f.name.toString(),
+ Reference.typeFromDescriptor(f.type.toDescriptorString())));
+ });
}
KeepRuleGraphNode getKeepRuleGraphNode(ProguardKeepRule rule) {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTest.java
new file mode 100644
index 0000000..5992eff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTest.java
@@ -0,0 +1,25 @@
+// 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 com.android.tools.r8.NeverInline;
+
+public class RemovedClassTest {
+
+ public static class RemovedInnerClass {}
+
+ public static void main(String[] args) {
+ bar();
+ }
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("called bar");
+ }
+
+ @NeverInline
+ public static void baz() {
+ System.out.println("called baz, created " + new RemovedClassTest().getClass().getSimpleName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTestRunner.java
new file mode 100644
index 0000000..c555b7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTestRunner.java
@@ -0,0 +1,84 @@
+// 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 com.android.tools.r8.references.Reference.classFromClass;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+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 com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+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 RemovedClassTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = RemovedClassTest.class;
+ private static final Class<?> REMOVED_CLASS = RemovedClassTest.RemovedInnerClass.class;
+ private static final List<Class<?>> CLASSES = ImmutableList.of(CLASS, REMOVED_CLASS);
+
+ private static final String EXPECTED = StringUtils.lines("called bar");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public RemovedClassTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void testRemovedClass() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ MethodReference barMethod = methodFromMethod(CLASS.getDeclaredMethod("bar"));
+ MethodReference bazMethod = methodFromMethod(CLASS.getDeclaredMethod("baz"));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ R8TestCompileResult compileResult =
+ testForR8(backend)
+ .enableGraphInspector()
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepMethodRules(mainMethod)
+ .addKeepRules("-whyareyoukeeping class " + REMOVED_CLASS.getTypeName())
+ .redirectStdOut(new PrintStream(baos))
+ .compile();
+ String expectedOutput = StringUtils.lines("Nothing is keeping " + REMOVED_CLASS.getTypeName());
+ String compileOutput = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+ assertEquals(expectedOutput, compileOutput);
+
+ GraphInspector inspector =
+ compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED).graphInspector();
+
+ // The only root should be the keep main-method rule.
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ // Check that the call chain goes from root -> main(unchanged) -> bar(renamed).
+ inspector.method(barMethod).assertRenamed().assertInvokedFrom(mainMethod);
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(root);
+
+ // Check baz is removed.
+ inspector.method(bazMethod).assertAbsent();
+
+ // Check that the inner class is removed.
+ inspector.clazz(classFromClass(REMOVED_CLASS)).assertAbsent();
+ }
+}