Add minification bit to keep info.

Bug: 157012327
Change-Id: I7d31564673fbaeb558f23aaabfacfb1096ab2ff8
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 3adb81d..245a9ab 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
@@ -47,6 +48,9 @@
   private GraphLense graphLense;
   private InitClassLens initClassLens;
   private RootSet rootSet;
+  // This should perferably always be obtained via AppInfoWithLiveness.
+  // Currently however the liveness may be downgraded thus loosing the computed keep info.
+  private KeepInfoCollection keepInfo = null;
   private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
   private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
       new InstanceFieldInitializationInfoFactory();
@@ -176,6 +180,9 @@
     if (appInfo != previous) {
       previous.markObsolete();
     }
+    if (appInfo.hasLiveness()) {
+      keepInfo = appInfo.withLiveness().getKeepInfo();
+    }
     @SuppressWarnings("unchecked")
     AppView<U> appViewWithSpecializedAppInfo = (AppView<U>) this;
     return appViewWithSpecializedAppInfo;
@@ -377,6 +384,10 @@
     this.rootSet = rootSet;
   }
 
+  public KeepInfoCollection getKeepInfo() {
+    return keepInfo;
+  }
+
   public MergedClassesCollection allMergedClasses() {
     MergedClassesCollection collection = new MergedClassesCollection();
     if (horizontallyMergedLambdaClasses != null) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index f617c31..04ac91a 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -919,9 +919,7 @@
 
   public boolean isAccessModificationAllowed(DexReference reference) {
     assert options().getProguardConfiguration().isAccessModificationAllowed();
-    return keepInfo
-        .getInfo(reference, this)
-        .isAccessModificationAllowed(options().getProguardConfiguration());
+    return keepInfo.getInfo(reference, this).isAccessModificationAllowed(options());
   }
 
   public boolean isPinned(DexReference reference) {
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 c66940e..e033e78 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -92,6 +92,7 @@
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
 import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.ItemsWithRules;
@@ -630,8 +631,13 @@
 
   private void enqueueRootClass(
       DexProgramClass clazz, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
     keepClassWithRules(clazz, rules);
+    enqueueKeepRuleInstantiatedType(clazz, rules, precondition);
+  }
+
+  private void enqueueKeepRuleInstantiatedType(
+      DexProgramClass clazz, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+    KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
     if (clazz.isAnnotation()) {
       workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness);
     } else if (clazz.isInterface()) {
@@ -743,11 +749,12 @@
   }
 
   private void compatEnqueueHolderIfDependentNonStaticMember(
-      DexClass holder, Set<ProguardKeepRuleBase> compatRules) {
+      DexProgramClass holder, Set<ProguardKeepRuleBase> compatRules) {
     if (!forceProguardCompatibility || compatRules == null) {
       return;
     }
-    enqueueRootItem(holder, compatRules);
+    // TODO(b/120959039): This needs the set of instance member as preconditon.
+    enqueueKeepRuleInstantiatedType(holder, compatRules, null);
   }
 
   //
@@ -1583,7 +1590,7 @@
 
     rootSet.forEachDependentInstanceConstructor(
         holder, appView, this::enqueueHolderWithDependentInstanceConstructor);
-    rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
+    rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentMember);
     compatEnqueueHolderIfDependentNonStaticMember(
         holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
 
@@ -1648,14 +1655,17 @@
     }
   }
 
-  private void enqueueDependentItem(
-      DexDefinition precondition, DexDefinition consequent, Set<ProguardKeepRuleBase> reasons) {
+  private void enqueueDependentMember(
+      DexDefinition precondition,
+      DexEncodedMember<?, ?> consequent,
+      Set<ProguardKeepRuleBase> reasons) {
     internalEnqueueRootItem(consequent, reasons, precondition);
   }
 
   private void enqueueHolderWithDependentInstanceConstructor(
       ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) {
-    enqueueRootItem(instanceInitializer.getHolder(), reasons);
+    DexProgramClass holder = instanceInitializer.getHolder();
+    enqueueKeepRuleInstantiatedType(holder, reasons, instanceInitializer.getDefinition());
   }
 
   private void processAnnotations(DexProgramClass holder, DexDefinition annotatedItem) {
@@ -2246,7 +2256,7 @@
   private void transitionDependentItemsForInstantiatedItem(DexProgramClass clazz) {
     do {
       // Handle keep rules that are dependent on the class being instantiated.
-      rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentItem);
+      rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentMember);
 
       // Visit the super type.
       clazz =
@@ -2658,6 +2668,24 @@
           new KotlinMetadataEnqueuerExtension(
               appView, enqueuerDefinitionSupplier, initialPrunedTypes));
     }
+    if (mode.isInitialTreeShaking()) {
+      // This is simulating the effect of the "root set" applied rules.
+      // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
+      // by iterating the instances.
+      for (DexReference reference : rootSet.noObfuscation) {
+        keepInfo.evaluateRule(reference, appInfo, Joiner::disallowMinification);
+      }
+    } else if (appView.getKeepInfo() != null) {
+      appView
+          .getKeepInfo()
+          .getRuleInstances()
+          .forEach(
+              (reference, rules) -> {
+                for (Consumer<Joiner<?, ?, ?>> rule : rules) {
+                  keepInfo.evaluateRule(reference, appInfo, rule);
+                }
+              });
+    }
     if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
       enqueueRootItems(rootSet.noShrinking);
     } else {
@@ -2688,43 +2716,35 @@
   }
 
   private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinClass(
-        clazz,
-        info ->
-            info.pin()
-                .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
+    keepInfo.joinClass(clazz, info -> applyKeepRules(rules, info));
   }
 
   private void keepMethodWithRules(
       DexProgramClass holder, DexEncodedMethod method, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinMethod(
-        holder,
-        method,
-        info ->
-            info.pin()
-                .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
+    keepInfo.joinMethod(holder, method, info -> applyKeepRules(rules, info));
   }
 
   private void keepFieldWithRules(
       DexProgramClass holder, DexEncodedField field, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinField(
-        holder,
-        field,
-        info ->
-            info.pin()
-                .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
+    keepInfo.joinField(holder, field, info -> applyKeepRules(rules, info));
   }
 
-  private boolean computeDisallowAccessModification(Set<ProguardKeepRuleBase> rules) {
+  private void applyKeepRules(Set<ProguardKeepRuleBase> rules, KeepInfo.Joiner<?, ?, ?> joiner) {
     for (ProguardKeepRuleBase rule : rules) {
       ProguardKeepRuleModifiers modifiers =
           (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
               .getModifiers();
+      if (!modifiers.allowsShrinking) {
+        // TODO(b/159589281): Evaluate this interpretation.
+        joiner.pin();
+      }
+      if (!modifiers.allowsObfuscation) {
+        joiner.disallowMinification();
+      }
       if (!modifiers.allowsAccessModification) {
-        return true;
+        joiner.disallowAccessModification();
       }
     }
-    return false;
   }
 
   private static class SyntheticAdditions {
@@ -3302,11 +3322,11 @@
             consequentRootSet.forEachDependentInstanceConstructor(
                 clazz, appView, this::enqueueHolderWithDependentInstanceConstructor);
             consequentRootSet.forEachDependentStaticMember(
-                clazz, appView, this::enqueueDependentItem);
+                clazz, appView, this::enqueueDependentMember);
             if (objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(
                 clazz)) {
               consequentRootSet.forEachDependentNonStaticMember(
-                  clazz, appView, this::enqueueDependentItem);
+                  clazz, appView, this::enqueueDependentMember);
             }
             compatEnqueueHolderIfDependentNonStaticMember(
                 clazz, consequentRootSet.getDependentKeepClassCompatRule(clazz.type));
@@ -3321,13 +3341,18 @@
         });
     // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
     rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking);
+    if (mode.isInitialTreeShaking()) {
+      for (DexReference reference : consequentRootSet.noObfuscation) {
+        keepInfo.evaluateRule(reference, appView, Joiner::disallowMinification);
+      }
+    }
     enqueueRootItems(consequentRootSet.noShrinking);
     // Check for compatibility rules indicating that the holder must be implicitly kept.
     if (forceProguardCompatibility) {
       consequentRootSet.dependentKeepClassCompatRule.forEach(
           (precondition, compatRules) -> {
             assert precondition.isDexType();
-            DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
+            DexProgramClass preconditionHolder = getProgramClassOrNull(precondition.asDexType());
             compatEnqueueHolderIfDependentNonStaticMember(preconditionHolder, compatRules);
           });
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
new file mode 100644
index 0000000..18ae09e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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;
+
+/** Globally controlled settings that affect the default values for kept items. */
+public interface GlobalKeepInfoConfiguration {
+
+  boolean isTreeShakingEnabled();
+
+  boolean isMinificationEnabled();
+
+  boolean isAccessModificationEnabled();
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 761547a..a1f8424 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -4,21 +4,23 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.shaking.KeepInfo.Builder;
-import java.util.function.Supplier;
 
 /** Keep information that can be associated with any item, i.e., class, method or field. */
 public abstract class KeepInfo<B extends Builder, K extends KeepInfo> {
 
   private final boolean pinned;
+  private final boolean allowMinification;
   private final boolean allowAccessModification;
 
-  private KeepInfo(boolean pinned, boolean allowAccessModification) {
+  private KeepInfo(boolean pinned, boolean allowMinification, boolean allowAccessModification) {
     this.pinned = pinned;
+    this.allowMinification = allowMinification;
     this.allowAccessModification = allowAccessModification;
   }
 
   KeepInfo(B builder) {
-    this(builder.isPinned(), builder.isAccessModificationAllowed());
+    this(
+        builder.isPinned(), builder.isMinificationAllowed(), builder.isAccessModificationAllowed());
   }
 
   /** True if an item must be present in the output. */
@@ -27,6 +29,20 @@
   }
 
   /**
+   * True if an item may have its name minified/changed.
+   *
+   * <p>This method requires knowledge of the global configuration as that can override the concrete
+   * value on a given item.
+   */
+  public boolean isMinificationAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isMinificationEnabled() && internalIsMinificationAllowed();
+  }
+
+  boolean internalIsMinificationAllowed() {
+    return allowMinification;
+  }
+
+  /**
    * True if an item may have its access flags modified.
    *
    * <p>This method requires knowledge of the global access modification as that will override the
@@ -34,8 +50,8 @@
    *
    * @param configuration Global configuration object to determine access modification.
    */
-  public boolean isAccessModificationAllowed(ProguardConfiguration configuration) {
-    return configuration.isAccessModificationAllowed() && internalIsAccessModificationAllowed();
+  public boolean isAccessModificationAllowed(GlobalKeepInfoConfiguration configuration) {
+    return configuration.isAccessModificationEnabled() && internalIsAccessModificationAllowed();
   }
 
   // Internal accessor for the items access-modification bit.
@@ -69,6 +85,7 @@
 
     private K original;
     private boolean pinned;
+    private boolean allowMinification;
     private boolean allowAccessModification;
 
     Builder() {
@@ -78,17 +95,20 @@
     Builder(K original) {
       this.original = original;
       pinned = original.isPinned();
+      allowMinification = original.internalIsMinificationAllowed();
       allowAccessModification = original.internalIsAccessModificationAllowed();
     }
 
     B makeTop() {
       pin();
+      disallowMinification();
       disallowAccessModification();
       return self();
     }
 
     B makeBottom() {
       unpin();
+      allowMinification();
       allowAccessModification();
       return self();
     }
@@ -110,6 +130,7 @@
 
     private boolean internalIsEqualTo(K other) {
       return isPinned() == other.isPinned()
+          && isMinificationAllowed() == other.internalIsMinificationAllowed()
           && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
           && isEqualTo(other);
     }
@@ -118,6 +139,10 @@
       return pinned;
     }
 
+    public boolean isMinificationAllowed() {
+      return allowMinification;
+    }
+
     public boolean isAccessModificationAllowed() {
       return allowAccessModification;
     }
@@ -135,6 +160,19 @@
       return setPinned(false);
     }
 
+    public B setAllowMinification(boolean allowMinification) {
+      this.allowMinification = allowMinification;
+      return self();
+    }
+
+    public B allowMinification() {
+      return setAllowMinification(true);
+    }
+
+    public B disallowMinification() {
+      return setAllowMinification(false);
+    }
+
     public B setAllowAccessModification(boolean allowAccessModification) {
       this.allowAccessModification = allowAccessModification;
       return self();
@@ -175,12 +213,13 @@
       return self();
     }
 
-    // Lazy modification of access modification.
-    // Only forced if access modification is still allowed.
-    public J lazyDisallowAccessModification(Supplier<Boolean> lazyShouldDisallow) {
-      if (builder.isAccessModificationAllowed() && lazyShouldDisallow.get()) {
-        builder.disallowAccessModification();
-      }
+    public J disallowMinification() {
+      builder.disallowMinification();
+      return self();
+    }
+
+    public J disallowAccessModification() {
+      builder.disallowAccessModification();
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index b85c71d..53af669 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -20,8 +20,10 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 
@@ -43,6 +45,8 @@
     return KeepFieldInfo.bottom();
   }
 
+  abstract Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances();
+
   /**
    * Base accessor for keep info on a class.
    *
@@ -127,6 +131,14 @@
     return getFieldInfo(field, definitions).isPinned();
   }
 
+  public final boolean isMinificationAllowed(
+      DexReference reference,
+      DexDefinitionSupplier definitions,
+      GlobalKeepInfoConfiguration configuration) {
+    return configuration.isMinificationEnabled()
+        && getInfo(reference, definitions).isMinificationAllowed(configuration);
+  }
+
   public final boolean verifyNoneArePinned(Collection<DexType> types, AppInfo appInfo) {
     for (DexType type : types) {
       DexProgramClass clazz =
@@ -161,17 +173,26 @@
     private final Map<DexMethod, KeepMethodInfo> keepMethodInfo;
     private final Map<DexField, KeepFieldInfo> keepFieldInfo;
 
+    // Map of applied rules for which keys may need to be mutated.
+    private final Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances;
+
     MutableKeepInfoCollection() {
-      this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+      this(
+          new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
+          new IdentityHashMap<>());
     }
 
     private MutableKeepInfoCollection(
         Map<DexType, KeepClassInfo> keepClassInfo,
         Map<DexMethod, KeepMethodInfo> keepMethodInfo,
-        Map<DexField, KeepFieldInfo> keepFieldInfo) {
+        Map<DexField, KeepFieldInfo> keepFieldInfo,
+        Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances) {
       this.keepClassInfo = keepClassInfo;
       this.keepMethodInfo = keepMethodInfo;
       this.keepFieldInfo = keepFieldInfo;
+      this.ruleInstances = ruleInstances;
     }
 
     @Override
@@ -197,7 +218,43 @@
             assert !info.isPinned() || field == newField;
             newFieldInfo.put(newField, info);
           });
-      return new MutableKeepInfoCollection(newClassInfo, newMethodInfo, newFieldInfo);
+      Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> newRuleInstances =
+          new IdentityHashMap<>(ruleInstances.size());
+      ruleInstances.forEach(
+          (reference, consumers) -> {
+            DexReference newReference;
+            if (reference.isDexType()) {
+              DexType newType = lens.lookupType(reference.asDexType());
+              if (!newType.isClassType()) {
+                assert newType.isIntType() : "Expected only enum unboxing type changes.";
+                return;
+              }
+              newReference = newType;
+            } else if (reference.isDexMethod()) {
+              newReference = lens.getRenamedMethodSignature(reference.asDexMethod());
+            } else {
+              assert reference.isDexField();
+              newReference = lens.getRenamedFieldSignature(reference.asDexField());
+            }
+            newRuleInstances.put(newReference, consumers);
+          });
+      return new MutableKeepInfoCollection(
+          newClassInfo, newMethodInfo, newFieldInfo, newRuleInstances);
+    }
+
+    @Override
+    Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances() {
+      return ruleInstances;
+    }
+
+    void evaluateRule(
+        DexReference reference,
+        DexDefinitionSupplier definitions,
+        Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
+      joinInfo(reference, definitions, fn);
+      if (!getInfo(reference, definitions).isBottom()) {
+        ruleInstances.computeIfAbsent(reference, k -> new ArrayList<>()).add(fn);
+      }
     }
 
     @Override
@@ -230,6 +287,34 @@
       }
     }
 
+    public void joinInfo(
+        DexReference reference,
+        DexDefinitionSupplier definitions,
+        Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
+      if (reference.isDexType()) {
+        DexType type = reference.asDexType();
+        DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
+        if (clazz != null) {
+          joinClass(clazz, fn::accept);
+        }
+      } else if (reference.isDexMethod()) {
+        DexMethod method = reference.asDexMethod();
+        DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(method.holder));
+        DexEncodedMethod definition = method.lookupOnClass(clazz);
+        if (definition != null) {
+          joinMethod(clazz, definition, fn::accept);
+        }
+      } else {
+        assert reference.isDexField();
+        DexField field = reference.asDexField();
+        DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(field.holder));
+        DexEncodedField definition = field.lookupOnClass(clazz);
+        if (definition != null) {
+          joinField(clazz, definition, fn::accept);
+        }
+      }
+    }
+
     public void keepClass(DexProgramClass clazz) {
       joinClass(clazz, KeepInfo.Joiner::top);
     }
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 b38d077..09a6587 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -29,10 +29,12 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
 import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
@@ -1368,7 +1370,7 @@
     public void forEachDependentMember(
         DexDefinition item,
         AppView<?> appView,
-        Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
+        Consumer3<DexDefinition, DexEncodedMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
       getDependentItems(item)
           .forEachMember(
               (reference, reasons) -> {
@@ -1386,7 +1388,7 @@
     public void forEachDependentNonStaticMember(
         DexDefinition item,
         AppView<?> appView,
-        Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
+        Consumer3<DexDefinition, DexEncodedMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
       forEachDependentMember(
           item,
           appView,
@@ -1400,7 +1402,7 @@
     public void forEachDependentStaticMember(
         DexDefinition item,
         AppView<?> appView,
-        Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
+        Consumer3<DexDefinition, DexEncodedMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
       forEachDependentMember(
           item,
           appView,
@@ -1904,21 +1906,50 @@
     }
 
     public boolean mayBeMinified(DexReference reference, AppView<?> appView) {
-      return !mayNotBeMinified(reference, appView);
+      boolean minificationAllowed =
+          appView.getKeepInfo().isMinificationAllowed(reference, appView, appView.options());
+      // TODO: Remove this once consistency is established.
+      //  Assert consistency with existing root set info.
+      //  Split in to checks to make the direction clean in errors.
+      // minify -> not-in-set
+      assert !minificationAllowed
+          || !noObfuscation.contains(getOriginal(reference, appView))
+          // Compiler synthesized methods can be renamed regardless of keep rules.
+          || isCompilerSynthesizedMethod(reference, appView);
+      // !minify -> in-set
+      assert minificationAllowed
+          || noObfuscation.contains(getOriginal(reference, appView))
+          || !appView.options().isMinificationEnabled();
+      return minificationAllowed;
     }
 
     public boolean mayNotBeMinified(DexReference reference, AppView<?> appView) {
-      if (reference.isDexType()) {
-        return noObfuscation.contains(
-            appView.graphLense().getOriginalType(reference.asDexType()));
-      } else if (reference.isDexMethod()) {
-        return noObfuscation.contains(
-            appView.graphLense().getOriginalMethodSignature(reference.asDexMethod()));
-      } else {
-        assert reference.isDexField();
-        return noObfuscation.contains(
-            appView.graphLense().getOriginalFieldSignature(reference.asDexField()));
+      return !mayBeMinified(reference, appView);
+    }
+
+    private boolean isCompilerSynthesizedMethod(DexReference reference, AppView<?> appView) {
+      DexMethod method = reference.asDexMethod();
+      if (method == null) {
+        return false;
       }
+      DexEncodedMethod definition = method.lookupOnClass(appView.definitionForHolder(method));
+      return definition != null
+          && (definition.isD8R8Synthesized()
+              || definition
+                  .qualifiedName()
+                  .contains(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME));
+    }
+
+    private DexReference getOriginal(DexReference reference, AppView<?> appView) {
+      GraphLense lens = appView.graphLense();
+      if (reference.isDexType()) {
+        return lens.getOriginalType(reference.asDexType());
+      }
+      if (reference.isDexMethod()) {
+        return lens.getOriginalMethodSignature(reference.asDexMethod());
+      }
+      assert reference.isDexField();
+      return lens.getOriginalFieldSignature(reference.asDexField());
     }
 
     public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 35e4614..f7353d9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -45,6 +45,7 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.GlobalKeepInfoConfiguration;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
@@ -77,7 +78,7 @@
 import java.util.function.Predicate;
 import org.objectweb.asm.Opcodes;
 
-public class InternalOptions {
+public class InternalOptions implements GlobalKeepInfoConfiguration {
 
   // Set to true to run compilation in a single thread and without randomly shuffling the input.
   // This makes life easier when running R8 in a debugger.
@@ -481,13 +482,33 @@
   private final boolean enableMinification;
 
   public boolean isShrinking() {
+    assert proguardConfiguration == null
+        || enableTreeShaking == proguardConfiguration.isShrinking();
     return enableTreeShaking;
   }
 
   public boolean isMinifying() {
+    assert proguardConfiguration == null
+        || enableMinification == proguardConfiguration.isObfuscating();
     return enableMinification;
   }
 
+  @Override
+  public boolean isTreeShakingEnabled() {
+    return isShrinking();
+  }
+
+  @Override
+  public boolean isMinificationEnabled() {
+    return isMinifying();
+  }
+
+  @Override
+  public boolean isAccessModificationEnabled() {
+    return getProguardConfiguration() != null
+        && getProguardConfiguration().isAccessModificationAllowed();
+  }
+
   public boolean keepInnerClassStructure() {
     return getProguardConfiguration().getKeepAttributes().signature
         || getProguardConfiguration().getKeepAttributes().innerClasses;
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index ed0e8f1..72a22e6 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -34,6 +34,18 @@
 
   TestDiagnosticMessages assertErrorsCount(int count);
 
+  default TestDiagnosticMessages assertNoInfos() {
+    return assertInfosCount(0);
+  }
+
+  default TestDiagnosticMessages assertNoWarnings() {
+    return assertWarningsCount(0);
+  }
+
+  default TestDiagnosticMessages assertNoErrors() {
+    return assertErrorsCount(0);
+  }
+
   // Match exact.
 
   default TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic> matcher) {
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index b0c7ecf..df38cb2 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -3,25 +3,27 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.checkdiscarded;
 
-import static org.hamcrest.MatcherAssert.assertThat;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
 import com.android.tools.r8.checkdiscarded.testclasses.Main;
 import com.android.tools.r8.checkdiscarded.testclasses.UnusedClass;
 import com.android.tools.r8.checkdiscarded.testclasses.UsedClass;
 import com.android.tools.r8.checkdiscarded.testclasses.WillBeGone;
 import com.android.tools.r8.checkdiscarded.testclasses.WillStay;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
 import org.junit.Test;
@@ -32,19 +34,19 @@
 @RunWith(Parameterized.class)
 public class CheckDiscardedTest extends TestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  @Parameters(name = "{0}, minify:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
   }
 
-  private final TestParameters parameters;
+  public final boolean minify;
 
-  public CheckDiscardedTest(TestParameters parameters) {
-    this.parameters = parameters;
+  public CheckDiscardedTest(TestParameters parameters, boolean minify) {
+    assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+    this.minify = minify;
   }
 
   private void compile(
-      boolean obfuscate,
       Class annotation,
       boolean checkMembers,
       Consumer<TestDiagnosticMessages> onCompilationFailure) {
@@ -56,7 +58,7 @@
               .addProgramClasses(UnusedClass.class, UsedClass.class, Main.class)
               .addKeepMainRule(Main.class)
               .addKeepRules(checkDiscardRule(checkMembers, annotation))
-              .minification(obfuscate)
+              .minification(minify)
               .addOptionsModification(this::noInlining)
               .compile();
       assertNull(onCompilationFailure);
@@ -80,45 +82,44 @@
 
   @Test
   public void classesAreGone() {
-    compile(false, WillBeGone.class, false, null);
-    compile(true, WillBeGone.class, false, null);
+    compile(WillBeGone.class, false, null);
   }
 
   @Test
   public void classesAreNotGone() {
     Consumer<TestDiagnosticMessages> check =
-        diagnostics -> {
-          List<Diagnostic> infos = diagnostics.getInfos();
-          assertEquals(2, infos.size());
-          String messageUsedClass = infos.get(1).getDiagnosticMessage();
-          assertThat(messageUsedClass, containsString("UsedClass was not discarded"));
-          assertThat(messageUsedClass, containsString("is instantiated in"));
-          String messageMain = infos.get(0).getDiagnosticMessage();
-          assertThat(messageMain, containsString("Main was not discarded"));
-          assertThat(messageMain, containsString("is referenced in keep rule"));
-        };
-    compile(false, WillStay.class, false, check);
-    compile(true, WillStay.class, false, check);
+        diagnostics ->
+            diagnostics
+                .assertNoWarnings()
+                .assertErrorsMatch(diagnosticMessage(containsString("Discard checks failed")))
+                .assertInfosMatch(
+                    ImmutableList.of(
+                        allOf(
+                            diagnosticMessage(containsString("UsedClass was not discarded")),
+                            diagnosticMessage(containsString("is instantiated in"))),
+                        allOf(
+                            diagnosticMessage(containsString("Main was not discarded")),
+                            diagnosticMessage(containsString("is referenced in keep rule")))));
+    compile(WillStay.class, false, check);
   }
 
   @Test
   public void membersAreGone() {
-    compile(false, WillBeGone.class, true, null);
-    compile(true, WillBeGone.class, true, null);
+    compile(WillBeGone.class, true, null);
   }
 
   @Test
   public void membersAreNotGone() {
     Consumer<TestDiagnosticMessages> check =
-        diagnostics -> {
-          List<Diagnostic> infos = diagnostics.getInfos();
-          assertEquals(1, infos.size());
-          String message = infos.get(0).getDiagnosticMessage();
-          assertThat(message, containsString("was not discarded"));
-          assertThat(message, containsString("is invoked from"));
-        };
-    compile(false, WillStay.class, true, check);
-    compile(true, WillStay.class, true, check);
+        diagnostics ->
+            diagnostics
+                .assertNoWarnings()
+                .assertErrorsMatch(diagnosticMessage(containsString("Discard checks failed")))
+                .assertInfosMatch(
+                    allOf(
+                        diagnosticMessage(containsString("was not discarded")),
+                        diagnosticMessage(containsString("is invoked from"))));
+    compile(WillStay.class, true, check);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
index 550957e..e6baeae 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
@@ -40,6 +40,7 @@
   @Test
   public void testIProguard() throws CompilationFailedException, IOException, ExecutionException {
     testForProguard()
+        // TODO(b/159694276): Run the resulting code on runtime.
         .addProgramClasses(I.class)
         .addKeepRules(
             "-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }",
@@ -51,6 +52,7 @@
 
   @Test
   public void testIR8() throws CompilationFailedException, IOException, ExecutionException {
+    // TODO(b/159694276): Add compat variant of this.
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class)
         .addKeepRules("-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }")
@@ -75,6 +77,7 @@
 
   @Test
   public void testAR8() throws CompilationFailedException, IOException, ExecutionException {
+    // TODO(b/159694276): Add non-compat variant of this.
     testForR8Compat(parameters.getBackend())
         .addProgramClasses(I.class, A.class, B.class, Main.class)
         .addKeepRules("-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }")