Merge "Reland "Load all library and program classes when running R8""
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3cf4664..228c0e5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -445,9 +445,6 @@
         appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness, options).run());
         appViewWithLiveness.setAppInfo(
             new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
-
-        // TODO(b/79143143): re-enable once fixed.
-        // graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
       }
 
       timing.begin("Create IR");
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
index e7282e1..0db0ca4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -43,13 +43,13 @@
   }
 
   @Override
-  public boolean isNull() {
+  public boolean isNullType() {
     return type == DexItemFactory.nullValueType;
   }
 
   @Override
   public TypeLatticeElement asNullable() {
-    assert isNull();
+    assert isNullType();
     return this;
   }
 
@@ -88,7 +88,7 @@
 
   @Override
   public int hashCode() {
-    assert isNull();
+    assert isNullType();
     return System.identityHashCode(this);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index b29cd31..ad8edd3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -29,7 +29,6 @@
   public static final ReferenceTypeLatticeElement NULL =
       ReferenceTypeLatticeElement.getNullTypeLatticeElement();
 
-
   // TODO(b/72693244): Switch to NullLatticeElement.
   private final boolean isNullable;
 
@@ -42,7 +41,7 @@
   }
 
   public NullLatticeElement nullElement() {
-    if (isNull()) {
+    if (isNullType()) {
       return NullLatticeElement.definitelyNull();
     }
     if (!isNullable()) {
@@ -91,10 +90,10 @@
     if (isTop() || other.isTop()) {
       return TOP;
     }
-    if (isNull()) {
+    if (isNullType()) {
       return other.asNullable();
     }
-    if (other.isNull()) {
+    if (other.isNullType()) {
       return asNullable();
     }
     if (isPrimitive()) {
@@ -270,7 +269,7 @@
   public boolean isPreciseType() {
     return isArrayType()
         || isClassType()
-        || isNull()
+        || isNullType()
         || isInt()
         || isFloat()
         || isLong()
@@ -286,21 +285,13 @@
   }
 
   /**
-   * Should use {@link #isConstantNull()} or {@link #isDefinitelyNull()} instead.
-   */
-  @Deprecated
-  public boolean isNull() {
-    return false;
-  }
-
-  /**
    * Determines if this type only includes null values that are defined by a const-number
    * instruction in the same enclosing method.
    *
    * These null values can be assigned to any type.
    */
-  public boolean isConstantNull() {
-    return isNull();
+  public boolean isNullType() {
+    return false;
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index c68baf9..596fdb8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -148,7 +148,7 @@
       assert inType.nullElement().lessThanOrEqual(outType.nullElement());
 
       // Since we cannot remove the cast the in-value must be different from null.
-      assert !inType.isNull();
+      assert !inType.isNullType();
 
       // TODO(b/72693244): Consider checking equivalence. This requires that the types are always
       // as precise as possible, though, meaning that almost all changes to the IR must be followed
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 7a6a9a4..3976889 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -302,7 +302,7 @@
     assert super.verifyTypes(appInfo, graphLense);
     assert !isZero()
         || outValue().getTypeLattice().isPrimitive()
-        || outValue().getTypeLattice().isConstantNull();
+        || outValue().getTypeLattice().isNullType();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index c9b12f2..23bb76c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -82,7 +82,7 @@
       return true;
     }
     TypeLatticeElement exceptionType = exception().getTypeLattice();
-    if (exceptionType.isConstantNull()) {
+    if (exceptionType.isNullType()) {
       // throw null
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 2094584..b417c48 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -343,7 +343,7 @@
         // redundant
         !knownToBeNonNullValue.isNeverNull()
         // v <- non-null NULL ?!
-        && !typeLattice.isConstantNull()
+        && !typeLattice.isNullType()
         // v <- non-null known-to-be-non-null // redundant
         && typeLattice.isNullable()
         // e.g., v <- non-null INT ?!
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 47b9289..9d2bb32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -36,6 +36,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
 
 public class UnusedArgumentsCollector {
 
@@ -89,7 +90,9 @@
         Streams.stream(appView.appInfo().classes())
             .map(this::runnableForClass)
             .map(executorService::submit)
-            .iterator());
+            // Materialize list such that all runnables are submitted to the executor service
+            // before calling awaitFutures().
+            .collect(Collectors.toList()));
 
     if (!methodMapping.isEmpty()) {
       return new UnusedArgumentsGraphLense(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index bd4f2b1..cdd85e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -305,7 +305,7 @@
           continue;
         }
         TypeLatticeElement inType = in.getTypeLattice();
-        if (inType.isConstantNull()) {
+        if (inType.isNullType()) {
           Value nullStringValue =
               code.createValue(TypeLatticeElement.stringClassType(appInfo), invoke.getLocalInfo());
           ConstString nullString = new ConstString(
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
deleted file mode 100644
index 86065d9..0000000
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright (c) 2017, 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.optimize;
-
-import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-public class BridgeMethodAnalysis {
-
-  private final GraphLense lense;
-  private final AppInfoWithLiveness appInfo;
-  private final Map<DexMethod, DexMethod> bridgeTargetToBridgeMap = new IdentityHashMap<>();
-
-  public BridgeMethodAnalysis(GraphLense lense, AppInfoWithLiveness appInfo) {
-    this.lense = lense;
-    this.appInfo = appInfo;
-  }
-
-  public GraphLense run() {
-    for (DexClass clazz : appInfo.classes()) {
-      clazz.forEachMethod(this::identifyBridgeMethod);
-    }
-    return new BridgeLense(lense, bridgeTargetToBridgeMap);
-  }
-
-  private void identifyBridgeMethod(DexEncodedMethod method) {
-    // The tree pruner can mark bridge methods abstract if they are not reachable but cannot
-    // be removed.
-    if (method.accessFlags.isBridge() && !method.accessFlags.isAbstract()) {
-      InvokeSingleTargetExtractor targetExtractor =
-          new InvokeSingleTargetExtractor(appInfo.dexItemFactory);
-      method.getCode().registerCodeReferences(targetExtractor);
-      DexMethod target = targetExtractor.getTarget();
-      InvokeKind kind = targetExtractor.getKind();
-      if (target != null && target.getArity() == method.method.getArity()) {
-        assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
-        if (kind == InvokeKind.STATIC) {
-          assert method.accessFlags.isStatic();
-          DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC).getMethod();
-          DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(actualTarget);
-          if (targetMethod != null) {
-            addForwarding(method, targetMethod);
-          }
-        } else if (kind == InvokeKind.VIRTUAL) {
-          // TODO(herhut): Add support for bridges with multiple targets.
-          DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL).getMethod();
-          DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(actualTarget);
-          if (targetMethod != null) {
-            addForwarding(method, targetMethod);
-          }
-        }
-      }
-    }
-  }
-
-  private void addForwarding(DexEncodedMethod method, DexEncodedMethod target) {
-    // This is a single target bridge we can inline.
-    if (Log.ENABLED) {
-      Log.info(getClass(), "Adding bridge forwarding %s -> %s.", method.method,
-          target.method);
-    }
-    // If we manage to rewrite all invocations, the bridge will be the only invocation of the target
-    // of the bridge and the target will get inlined. This should happen in most cases. For the few
-    // other cases, we might have inserted some extra checkcast instructions for the return type.
-    bridgeTargetToBridgeMap.put(target.method, method.method);
-  }
-
-
-  private static class BridgeLense extends GraphLense {
-
-    private final GraphLense previousLense;
-    private final Map<DexMethod, DexMethod> bridgeTargetToBridgeMap;
-
-    private BridgeLense(GraphLense previousLense,
-        Map<DexMethod, DexMethod> bridgeTargetToBridgeMap) {
-      this.previousLense = previousLense;
-      this.bridgeTargetToBridgeMap = bridgeTargetToBridgeMap;
-    }
-
-    @Override
-    public DexField getOriginalFieldSignature(DexField field) {
-      return previousLense.getOriginalFieldSignature(field);
-    }
-
-    @Override
-    public DexMethod getOriginalMethodSignature(DexMethod method) {
-      // TODO(b/79143143): implement this when re-enable bridge analysis.
-      throw new Unimplemented("BridgeLense.getOriginalMethodSignature() not implemented");
-    }
-
-    @Override
-    public DexField getRenamedFieldSignature(DexField originalField) {
-      return previousLense.getRenamedFieldSignature(originalField);
-    }
-
-    @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
-      // TODO(b/79143143): implement this when re-enabling bridge analysis.
-      throw new Unimplemented("BridgeLense.getRenamedMethodSignature() not implemented");
-    }
-
-    @Override
-    public DexType lookupType(DexType type) {
-      return previousLense.lookupType(type);
-    }
-
-    @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
-      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
-      DexMethod bridge = bridgeTargetToBridgeMap.get(previous.getMethod());
-      // Do not forward calls from a bridge method to itself while the bridge method is still
-      // a bridge.
-      if (bridge == null || (context.accessFlags.isBridge() && bridge == context.method)) {
-        return previous;
-      }
-      return new GraphLenseLookupResult(bridge, type);
-    }
-
-    @Override
-    public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
-      return previousLense.lookupPrototypeChanges(method);
-    }
-
-    @Override
-    public DexField lookupField(DexField field) {
-      return previousLense.lookupField(field);
-    }
-
-    @Override
-    public boolean isContextFreeForMethods() {
-      return false;
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder builder = new StringBuilder();
-      builder.append("------ BridgeMap ------").append(System.lineSeparator());
-      for (Map.Entry<DexMethod, DexMethod> entry : bridgeTargetToBridgeMap.entrySet()) {
-        builder.append(entry.getKey().toSourceString()).append(" -> ");
-        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
-      }
-      builder.append("-----------------------").append(System.lineSeparator());
-      builder.append(previousLense.toString());
-      return builder.toString();
-    }
-  }
-}
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/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index bde7016..fb4f409 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -15,11 +15,7 @@
 
   public static void awaitFutures(Iterable<? extends Future<?>> futures)
       throws ExecutionException {
-    awaitFutures(futures.iterator());
-  }
-
-  public static void awaitFutures(Iterator<? extends Future<?>> futureIterator)
-      throws ExecutionException {
+    Iterator<? extends Future<?>> futureIterator = futures.iterator();
     try {
       while (futureIterator.hasNext()) {
         futureIterator.next().get();
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;
     }
   }