Collect all root rules that keep a particular program item alive.
Bug: 122297131
Change-Id: Ide92a9af4e1acff47c6fb84985130a96e2073ff3
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 279d381..f10d000 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -297,11 +297,11 @@
this.options = options;
}
- private void enqueueRootItems(Map<DexDefinition, ProguardKeepRule> items) {
+ private void enqueueRootItems(Map<DexDefinition, Set<ProguardKeepRule>> items) {
items.entrySet().forEach(this::enqueueRootItem);
}
- private void enqueueRootItem(Entry<DexDefinition, ProguardKeepRule> root) {
+ private void enqueueRootItem(Entry<DexDefinition, Set<ProguardKeepRule>> root) {
enqueueRootItem(root.getKey(), root.getValue());
}
@@ -309,7 +309,26 @@
enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
}
+ private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRule> rules) {
+ assert !rules.isEmpty();
+ if (keptGraphConsumer != null) {
+ GraphNode node = getGraphNode(item);
+ for (ProguardKeepRule rule : rules) {
+ registerEdge(node, KeepReason.dueToKeepRule(rule));
+ }
+ }
+ internalEnqueueRootItem(item, KeepReason.dueToKeepRule(rules.iterator().next()));
+ }
+
private void enqueueRootItem(DexDefinition item, KeepReason reason) {
+ if (keptGraphConsumer != null) {
+ registerEdge(getGraphNode(item), reason);
+ }
+ internalEnqueueRootItem(item, reason);
+ }
+
+ private void internalEnqueueRootItem(DexDefinition item, KeepReason reason) {
+ // TODO(b/120959039): do we need to propagate the reason to the action now?
if (item.isDexClass()) {
DexClass clazz = item.asDexClass();
workList.add(Action.markInstantiated(clazz, reason));
@@ -349,11 +368,11 @@
}
private void enqueueHolderIfDependentNonStaticMember(
- DexClass holder, Map<DexDefinition, ProguardKeepRule> dependentItems) {
+ DexClass holder, Map<DexDefinition, Set<ProguardKeepRule>> dependentItems) {
// Check if any dependent members are not static, and in that case enqueue the class as well.
// Having a dependent rule like -keepclassmembers with non static items indicates that class
// instances will be present even if tracing do not find any instantiation. See b/115867670.
- for (Entry<DexDefinition, ProguardKeepRule> entry : dependentItems.entrySet()) {
+ for (Entry<DexDefinition, Set<ProguardKeepRule>> entry : dependentItems.entrySet()) {
DexDefinition dependentItem = entry.getKey();
if (dependentItem.isDexClass()) {
continue;
@@ -791,7 +810,7 @@
annotations.forEach(this::handleAnnotationOfLiveType);
}
- Map<DexDefinition, ProguardKeepRule> dependentItems = rootSet.getDependentItems(holder);
+ Map<DexDefinition, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
enqueueHolderIfDependentNonStaticMember(holder, dependentItems);
// Add all dependent members to the workqueue.
enqueueRootItems(dependentItems);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 2f7f54a..e86966f 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -61,7 +61,7 @@
private final AppView<? extends AppInfo> appView;
private final DirectMappedDexApplication application;
private final Collection<ProguardConfigurationRule> rules;
- private final Map<DexDefinition, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
+ private final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
private final Set<DexDefinition> noOptimization = Sets.newIdentityHashSet();
private final Set<DexDefinition> noObfuscation = Sets.newIdentityHashSet();
private final LinkedHashMap<DexDefinition, DexDefinition> reasonAsked = new LinkedHashMap<>();
@@ -76,7 +76,7 @@
private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
- private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking =
+ private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexDefinition, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
private final Map<DexDefinition, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
@@ -854,8 +854,10 @@
return;
}
// Keep the type if the item is also kept.
- dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
- .put(definition, context);
+ dependentNoShrinking
+ .computeIfAbsent(item, x -> new IdentityHashMap<>())
+ .computeIfAbsent(definition, k -> new HashSet<>())
+ .add(context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
noObfuscation.add(definition);
}
@@ -890,10 +892,12 @@
ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
if (!modifiers.allowsShrinking) {
if (precondition != null) {
- dependentNoShrinking.computeIfAbsent(precondition, x -> new IdentityHashMap<>())
- .put(item, keepRule);
+ dependentNoShrinking
+ .computeIfAbsent(precondition, x -> new IdentityHashMap<>())
+ .computeIfAbsent(item, i -> new HashSet<>())
+ .add(keepRule);
} else {
- noShrinking.put(item, keepRule);
+ noShrinking.computeIfAbsent(item, i -> new HashSet<>()).add(keepRule);
}
}
if (!modifiers.allowsOptimization) {
@@ -970,7 +974,7 @@
public static class RootSet {
- public final Map<DexDefinition, ProguardKeepRule> noShrinking;
+ public final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
public final Set<DexDefinition> noOptimization;
public final Set<DexDefinition> noObfuscation;
public final ImmutableList<DexDefinition> reasonAsked;
@@ -985,12 +989,13 @@
public final Set<DexType> neverMerge;
public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
public final Map<DexDefinition, ProguardMemberRule> assumedValues;
- private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
+ private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>>
+ dependentNoShrinking;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
private RootSet(
- Map<DexDefinition, ProguardKeepRule> noShrinking,
+ Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
Set<DexDefinition> noOptimization,
Set<DexDefinition> noObfuscation,
ImmutableList<DexDefinition> reasonAsked,
@@ -1005,7 +1010,7 @@
Set<DexType> neverMerge,
Map<DexDefinition, ProguardMemberRule> noSideEffects,
Map<DexDefinition, ProguardMemberRule> assumedValues,
- Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking,
+ Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
@@ -1030,7 +1035,7 @@
// Add dependent items that depend on -if rules.
void addDependentItems(
- Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentItems) {
+ Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentItems) {
dependentItems.forEach(
(def, dependence) ->
dependentNoShrinking
@@ -1038,7 +1043,7 @@
.putAll(dependence));
}
- Map<DexDefinition, ProguardKeepRule> getDependentItems(DexDefinition item) {
+ Map<DexDefinition, Set<ProguardKeepRule>> getDependentItems(DexDefinition item) {
return Collections
.unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
}
@@ -1224,18 +1229,18 @@
static class ConsequentRootSet {
final Set<DexMethod> neverInline;
final Set<DexType> neverClassInline;
- final Map<DexDefinition, ProguardKeepRule> noShrinking;
+ final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
final Set<DexDefinition> noOptimization;
final Set<DexDefinition> noObfuscation;
- final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
+ final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking;
private ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
- Map<DexDefinition, ProguardKeepRule> noShrinking,
+ Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
Set<DexDefinition> noOptimization,
Set<DexDefinition> noObfuscation,
- Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
+ Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking) {
this.neverInline = Collections.unmodifiableSet(neverInline);
this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
this.noShrinking = Collections.unmodifiableMap(noShrinking);
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
index 19cdf04..a7e3e46 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
@@ -86,7 +86,7 @@
.method(fooMethod)
.assertNotRenamed()
.assertNotInvokedFrom(mainMethod)
- // TODO(b/122297131): keepAnnotatedMethods should also be keeping foo alive!
+ .assertKeptBy(keepAnnotatedMethods)
.assertKeptBy(keepClassesOfAnnotatedMethods);
// Check baz is removed.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTest.java
new file mode 100644
index 0000000..7cb464d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTest.java
@@ -0,0 +1,18 @@
+// 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.Keep;
+
+@Keep
+public class KeptByTwoRulesTest {
+
+ public static void foo() {
+ System.out.println("called foo");
+ }
+
+ public static void main(String[] args) {
+ foo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java
new file mode 100644
index 0000000..38c3f00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java
@@ -0,0 +1,87 @@
+// 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.TestBase;
+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 java.util.Arrays;
+import java.util.Collection;
+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 KeptByTwoRulesTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = KeptByTwoRulesTest.class;
+ private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+ private final String EXPECTED = StringUtils.lines("called foo");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptByTwoRulesTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ MethodReference fooMethod = methodFromMethod(CLASS.getDeclaredMethod("foo"));
+
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED);
+ }
+
+ String keepPublicRule = "-keep @com.android.tools.r8.Keep class * { public *; }";
+ String keepFooRule = "-keep class " + CLASS.getTypeName() + " { public void foo(); }";
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector()
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(keepPublicRule, keepFooRule)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED)
+ .graphInspector();
+
+ assertEquals(2, inspector.getRoots().size());
+ QueryNode keepPublic = inspector.rule(keepPublicRule).assertRoot();
+ QueryNode keepFoo = inspector.rule(keepFooRule).assertRoot();
+
+ inspector
+ .method(mainMethod)
+ .assertNotRenamed()
+ .assertKeptBy(keepPublic)
+ .assertNotKeptBy(keepFoo);
+
+ // Check foo is called from main and kept by two rules.
+ inspector
+ .method(fooMethod)
+ .assertNotRenamed()
+ .assertInvokedFrom(mainMethod)
+ .assertKeptBy(keepPublic)
+ .assertKeptBy(keepFoo);
+
+ // Check the class is also kept by both rules.
+ inspector
+ .clazz(classFromClass(CLASS))
+ .assertNotRenamed()
+ .assertKeptBy(keepPublic)
+ .assertKeptBy(keepFoo);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 107978b..940482b 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.graphinspector;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -113,16 +114,26 @@
}
public QueryNode assertNotInvokedFrom(MethodReference method) {
- assertTrue(
- errorMessage("no invocation from " + method.toString(), "invoke"),
- !isInvokedFrom(method));
+ assertFalse(
+ errorMessage("no invocation from " + method.toString(), "invoke"), isInvokedFrom(method));
return this;
}
public QueryNode assertKeptBy(QueryNode node) {
- assertTrue("Invalid call to assertKeptBy with: " + node.getNodeDescription(),
- node.isPresent());
- assertTrue(errorMessage("kept by " + node.getNodeDescription(), "none"), isKeptBy(node));
+ assertTrue(
+ "Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
+ assertTrue(
+ errorMessage("kept by " + node.getNodeDescription(), "was not kept by it"),
+ isKeptBy(node));
+ return this;
+ }
+
+ public QueryNode assertNotKeptBy(QueryNode node) {
+ assertTrue(
+ "Invalid call to assertNotKeptBy with: " + node.getNodeDescription(), node.isPresent());
+ assertFalse(
+ errorMessage("not kept by " + node.getNodeDescription(), "was kept by it"),
+ isKeptBy(node));
return this;
}
}