Add support for -if rule (without <n>th wildcards).

Fourth design (PS 21):

In RootSetBuilder, only -if rule itself is collected. A partial builder
that can share the same logic in RootSetBuilder is introduced to build
ConsequentRootSet, though.

In Enqueuer, using live items, both of -if conditional and consequent
parts are evaluated. Refer to 3rd design below for some other details.

------

Third design (PS15 ~ PS19):

In RootSetBuilder, only consequent -keep rule are evaluated to collect
which types/members will become live, depending on -if conditional rule.

In Enqueuer, which trace live items based on keep rules, -if conditional
rule is evaluated against newly-became-live items. Depending on the
modifiers of consequent rule, e.g., -keep*names, dependent items would
be enqueued, moved to no-optimization set or no-obfuscation set.

Abandoned to prepare back reference in mind.

------

Second design (PS13):

In RootSetBuilder, -if rule and consequent -keep rule are evaluated just
to collect which DexItem's, such as DexClass (will be hoisted to DexType
for minification), DexEncodedField and DexEncodedMethod, are bound.
Those bindings are maintained in a RootSet.

When Enqueuer records which type/field/method are live, the enablement
of involved -if rule is updated, and once enabled, dependent items are
either enqueued (-keep) or moved to no-obfuscation set (-keep*names).

Abandoned because member rules for any member (e.g., * or <fields>) are
not treated properly. They should be evaluated while tracing live items.

------

First design (PS1 ~ PS11):

In the first round of rule matching, root set builder will figure out
which types match with -if part. Then, in the second round, it will
iterate over subsequent part and bind matched types with precondition
types.

For -if/-keep*names that don't allow obfuscation, but allow shrinking,
dependentNoObfuscation, which mimics dependentNoShrinking, is added.

Abandoned because dependent items are *AND* logic, and both of
dependentNoShrinking and dependentNoObfuscation are bound to one item.

Bug: 73708139
Change-Id: I407ea9c6e20a7e49a36be66838696584c8968cfc
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index f525565..3db9699 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -40,9 +40,9 @@
         new ApplicationReader(app, options, timing).read(executor).toDirect();
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
     RootSet mainDexRootSet =
-        new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options).run(executor);
+        new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options).run(executor);
     Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
-    AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, timing);
+    AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
     // LiveTypes is the result.
     Set<DexType> mainDexClasses =
         new MainDexListBuilder(new HashSet<>(mainDexAppInfo.liveTypes), application).run();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d062d13..4f12ce1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -282,13 +282,13 @@
             ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
 
         rootSet = new RootSetBuilder(
-                    application, appInfo, options.proguardConfiguration.getRules(), options)
+                    appInfo, application, options.proguardConfiguration.getRules(), options)
                 .run(executorService);
         ProtoLiteExtension protoLiteExtension =
             options.forceProguardCompatibility ? null : new ProtoLiteExtension(appInfo);
         Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility,
             compatibility, protoLiteExtension);
-        appInfo = enqueuer.traceApplication(rootSet, timing);
+        appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
         if (options.proguardConfiguration.isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
@@ -396,9 +396,10 @@
         Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
         // Lets find classes which may have code executed before secondary dex files installation.
         RootSet mainDexRootSet =
-            new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options)
+            new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options)
                 .run(executorService);
-        AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, timing);
+        AppInfoWithLiveness mainDexAppInfo =
+            enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
 
         // LiveTypes is the result.
         Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
@@ -416,7 +417,7 @@
         timing.begin("Post optimization code stripping");
         try {
           Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
-          appInfo = enqueuer.traceApplication(rootSet, timing);
+          appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
           if (options.enableTreeShaking) {
             TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
             application = pruner.run();
diff --git a/src/main/java/com/android/tools/r8/shaking/DexStringCache.java b/src/main/java/com/android/tools/r8/shaking/DexStringCache.java
new file mode 100644
index 0000000..377dacd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DexStringCache.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, 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;
+
+import com.android.tools.r8.graph.DexString;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class DexStringCache {
+  private final ConcurrentHashMap<DexString, String> stringCache = new ConcurrentHashMap<>();
+
+  public String lookupString(DexString name) {
+    return stringCache.computeIfAbsent(name, DexString::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 a726391..3a31203 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
 import com.android.tools.r8.utils.InternalOptions;
@@ -70,6 +71,8 @@
 import java.util.Queue;
 import java.util.Set;
 import java.util.SortedSet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -128,7 +131,7 @@
       .newIdentityHashMap();
 
   /**
-   * Set of types that are mentioned in the program. We at least need an empty abstract classitem
+   * Set of types that are mentioned in the program. We at least need an empty abstract class item
    * for these.
    */
   private final Set<DexType> liveTypes = Sets.newIdentityHashSet();
@@ -1027,30 +1030,36 @@
         reachability, instantiatedTypes.getReasons());
   }
 
-  public AppInfoWithLiveness traceMainDex(RootSet rootSet, Timing timing) {
+  public AppInfoWithLiveness traceMainDex(
+      RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
     this.tracingMainDex = true;
     this.rootSet = rootSet;
     // Translate the result of root-set computation into enqueuer actions.
     enqueueRootItems(rootSet.noShrinking);
-    AppInfoWithLiveness appInfo = trace(timing);
+    AppInfoWithLiveness appInfo = trace(executorService, timing);
     options.reporter.failIfPendingErrors();
     return appInfo;
   }
 
-  public AppInfoWithLiveness traceApplication(RootSet rootSet, Timing timing) {
+  public AppInfoWithLiveness traceApplication(
+      RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
     this.rootSet = rootSet;
     // Translate the result of root-set computation into enqueuer actions.
     enqueueRootItems(rootSet.noShrinking);
     appInfo.libraryClasses().forEach(this::markAllLibraryVirtualMethodsReachable);
-    AppInfoWithLiveness result = trace(timing);
+    AppInfoWithLiveness result = trace(executorService, timing);
     options.reporter.failIfPendingErrors();
     return result;
   }
 
-  private AppInfoWithLiveness trace(Timing timing) {
+  private AppInfoWithLiveness trace(
+      ExecutorService executorService, Timing timing) throws ExecutionException {
     timing.begin("Grow the tree.");
     try {
       while (true) {
+        long numOfLiveItems = (long) liveTypes.size();
+        numOfLiveItems += (long) liveMethods.items.size();
+        numOfLiveItems += (long) liveFields.items.size();
         while (!workList.isEmpty()) {
           Action action = workList.poll();
           switch (action.kind) {
@@ -1083,6 +1092,24 @@
               throw new IllegalArgumentException(action.kind.toString());
           }
         }
+
+        // Continue fix-point processing if -if rules are enabled by items that newly became live.
+        long numOfLiveItemsAfterProcessing = (long) liveTypes.size();
+        numOfLiveItemsAfterProcessing += (long) liveMethods.items.size();
+        numOfLiveItemsAfterProcessing += (long) liveFields.items.size();
+        if (numOfLiveItemsAfterProcessing > numOfLiveItems) {
+          RootSetBuilder consequentSetBuilder =
+              new RootSetBuilder(appInfo, rootSet.ifRules, options);
+          ConsequentRootSet consequentRootSet = consequentSetBuilder.runForIfRules(
+              executorService, liveTypes, liveMethods.getItems(), liveFields.getItems());
+          enqueueRootItems(consequentRootSet.noShrinking);
+          rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
+          rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
+          if (!workList.isEmpty()) {
+            continue;
+          }
+        }
+
         // Continue fix-point processing while there are additional work items to ensure
         // Proguard compatibility.
         if (proguardCompatibilityWorkList.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 35c8e47..c1af9f4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -336,8 +336,6 @@
         configurationBuilder.addRule(parseIdentifierNameStringRule());
       } else if (acceptString("if")) {
         configurationBuilder.addRule(parseIfRule(optionStart));
-        // TODO(b/73708139): remove warning once we add support -if <class_spec>
-        warnIgnoringOptions("if", optionStart);
       } else {
         String unknownOption = acceptString();
         reporter.error(new StringDiagnostic(
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 6facec1..9659954 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,21 +5,19 @@
 
 import java.util.Set;
 
-public class ProguardIfRule extends ProguardConfigurationRule {
+public class ProguardIfRule extends ProguardKeepRule {
 
   final ProguardKeepRule subsequentRule;
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder extends ProguardKeepRule.Builder {
 
     ProguardKeepRule subsequentRule = null;
 
-    private Builder() {
-    }
-
     public void setSubsequentRule(ProguardKeepRule rule) {
       subsequentRule = rule;
     }
 
+    @Override
     public ProguardIfRule build() {
       assert subsequentRule != null : "Option -if without a subsequent rule.";
       return new ProguardIfRule(classAnnotation, classAccessFlags,
@@ -37,7 +35,8 @@
       Set<ProguardMemberRule> memberRules,
       ProguardKeepRule subsequentRule) {
     super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules,
+        ProguardKeepRuleType.CONDITIONAL, ProguardKeepRuleModifiers.builder().build());
     this.subsequentRule = subsequentRule;
   }
 
@@ -46,6 +45,23 @@
   }
 
   @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof ProguardIfRule)) {
+      return false;
+    }
+    ProguardIfRule other = (ProguardIfRule) o;
+    if (subsequentRule != other.subsequentRule) {
+      return false;
+    }
+    return super.equals(o);
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() * 3 + subsequentRule.hashCode();
+  }
+
+  @Override
   String typeString() {
     return "if";
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 9b1bc0b..d3d6705 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -14,7 +14,7 @@
     private final ProguardKeepRuleModifiers.Builder modifiersBuilder
         = ProguardKeepRuleModifiers.builder();
 
-    private Builder() {}
+    protected Builder() {}
 
     public void setType(ProguardKeepRuleType type) {
       this.type = type;
@@ -34,7 +34,7 @@
   private final ProguardKeepRuleType type;
   private final ProguardKeepRuleModifiers modifiers;
 
-  private ProguardKeepRule(
+  protected ProguardKeepRule(
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java
index 4af299c..2018cbf 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java
@@ -8,7 +8,8 @@
 public enum ProguardKeepRuleType {
   KEEP,
   KEEP_CLASS_MEMBERS,
-  KEEP_CLASSES_WITH_MEMBERS;
+  KEEP_CLASSES_WITH_MEMBERS,
+  CONDITIONAL;
 
   @Override
   public String toString() {
@@ -19,6 +20,8 @@
         return "keepclassmembers";
       case KEEP_CLASSES_WITH_MEMBERS:
         return "keepclasseswithmembers";
+      case CONDITIONAL:
+        return "if";
       default:
         throw new Unreachable("Unknown ProguardKeepRuleType.");
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index db7dca8..5e1293f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -156,7 +156,7 @@
     return type;
   }
 
-  public boolean matches(DexEncodedField field, RootSetBuilder builder) {
+  public boolean matches(DexEncodedField field, DexStringCache stringCache) {
     switch (getRuleType()) {
       case ALL:
       case ALL_FIELDS:
@@ -169,7 +169,7 @@
         return RootSetBuilder.containsAnnotation(annotation, field.annotations);
       case FIELD:
         // Name check.
-        String name = builder.lookupString(field.field.name);
+        String name = stringCache.lookupString(field.field.name);
         if (!getName().matches(name)) {
           break;
         }
@@ -196,7 +196,7 @@
     return false;
   }
 
-  public boolean matches(DexEncodedMethod method, RootSetBuilder builder) {
+  public boolean matches(DexEncodedMethod method, DexStringCache stringCache) {
     switch (getRuleType()) {
       case ALL_METHODS:
         if (method.isClassInitializer()) {
@@ -220,7 +220,7 @@
       case CONSTRUCTOR:
       case INIT:
         // Name check.
-        String name = builder.lookupString(method.method.name);
+        String name = stringCache.lookupString(method.method.name);
         if (!getName().matches(name)) {
           break;
         }
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 9c0dcdd..3912b1c 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.logging.Log;
@@ -40,17 +39,17 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 public class RootSetBuilder {
 
-  private final DirectMappedDexApplication application;
   private final AppInfo appInfo;
-  private final List<ProguardConfigurationRule> rules;
+  private final DirectMappedDexApplication application;
+  private final Collection<ProguardConfigurationRule> rules;
   private final Map<DexItem, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
   private final Set<DexItem> noOptimization = Sets.newIdentityHashSet();
   private final Set<DexItem> noObfuscation = Sets.newIdentityHashSet();
@@ -67,18 +66,37 @@
   private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
   private final InternalOptions options;
 
-  public RootSetBuilder(DexApplication application, AppInfo appInfo,
-      List<ProguardConfigurationRule> rules, InternalOptions options) {
-    this.application = application.asDirect();
+  private final DexStringCache dexStringCache = new DexStringCache();
+  private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
+
+  public RootSetBuilder(
+      AppInfo appInfo,
+      DexApplication application,
+      List<ProguardConfigurationRule> rules,
+      InternalOptions options) {
     this.appInfo = appInfo;
-    this.rules = rules;
+    this.application = application.asDirect();
+    this.rules = rules == null ? null : Collections.unmodifiableCollection(rules);
     this.options = options;
   }
 
-  private boolean anySuperTypeMatches(DexType type, ProguardTypeMatcher name,
+  RootSetBuilder(
+      AppInfo appInfo,
+      Set<ProguardIfRule> ifRules,
+      InternalOptions options) {
+    this.appInfo = appInfo;
+    this.application = appInfo.app.asDirect();
+    this.rules = Collections.unmodifiableCollection(ifRules);
+    this.options = options;
+  }
+
+  private boolean anySuperTypeMatches(
+      DexType type,
+      Function<DexType, DexClass> definitionFor,
+      ProguardTypeMatcher name,
       ProguardTypeMatcher annotation) {
     while (type != null) {
-      DexClass clazz = application.definitionFor(type);
+      DexClass clazz = definitionFor.apply(type);
       if (clazz == null) {
         // TODO(herhut): Warn about broken supertype chain?
         return false;
@@ -91,36 +109,42 @@
     return false;
   }
 
-  private boolean anyImplementedInterfaceMatches(DexClass clazz,
-      ProguardTypeMatcher className, ProguardTypeMatcher annotation) {
+  private boolean anyImplementedInterfaceMatches(
+      DexClass clazz,
+      Function<DexType, DexClass> definitionFor,
+      ProguardTypeMatcher className,
+      ProguardTypeMatcher annotation) {
     if (clazz == null) {
       return false;
     }
     for (DexType iface : clazz.interfaces.values) {
-      DexClass ifaceClass = application.definitionFor(iface);
+      DexClass ifaceClass = definitionFor.apply(iface);
       if (ifaceClass == null) {
         // TODO(herhut): Warn about broken supertype chain?
         return false;
       }
       // TODO(herhut): Maybe it would be better to do this breadth first.
       if ((className.matches(iface) && containsAnnotation(annotation, ifaceClass.annotations))
-          || anyImplementedInterfaceMatches(ifaceClass, className, annotation)) {
+          || anyImplementedInterfaceMatches(ifaceClass, definitionFor, className, annotation)) {
         return true;
       }
     }
     if (clazz.superType == null) {
       return false;
     }
-    DexClass superClass = application.definitionFor(clazz.superType);
+    DexClass superClass = definitionFor.apply(clazz.superType);
     if (superClass == null) {
       // TODO(herhut): Warn about broken supertype chain?
       return false;
     }
-    return anyImplementedInterfaceMatches(superClass, className, annotation);
+    return anyImplementedInterfaceMatches(superClass, definitionFor, className, annotation);
   }
 
   // Process a class with the keep rule.
-  private void process(DexClass clazz, ProguardConfigurationRule rule) {
+  private void process(
+      DexClass clazz,
+      ProguardConfigurationRule rule,
+      ProguardIfRule ifRule) {
     if (rule.getClassType().matches(clazz) == rule.getClassTypeNegated()) {
       return;
     }
@@ -140,12 +164,18 @@
     // TODO(herhut): One day make this do what it says.
     if (rule.hasInheritanceClassName()) {
       boolean extendsExpected =
-          anySuperTypeMatches(clazz.superType, rule.getInheritanceClassName(),
+          anySuperTypeMatches(
+              clazz.superType,
+              application::definitionFor,
+              rule.getInheritanceClassName(),
               rule.getInheritanceAnnotation());
       boolean implementsExpected = false;
       if (!extendsExpected) {
         implementsExpected =
-            anyImplementedInterfaceMatches(clazz, rule.getInheritanceClassName(),
+            anyImplementedInterfaceMatches(
+                clazz,
+                application::definitionFor,
+                rule.getInheritanceClassName(),
                 rule.getInheritanceAnnotation());
       }
       if (!extendsExpected && !implementsExpected) {
@@ -167,16 +197,15 @@
       }
     }
 
-    if (rule instanceof ProguardIfRule) {
-      // TODO(b/73708139): add support -if <class_spec>
-      // Check if -if part matches or subsequent -keep part matches.
-    } else if (rule.getClassNames().matches(clazz.type)) {
+    if (rule.getClassNames().matches(clazz.type)) {
       Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
       if (rule instanceof ProguardKeepRule) {
         switch (((ProguardKeepRule) rule).getType()) {
           case KEEP_CLASS_MEMBERS: {
-            markMatchingVisibleMethods(clazz, memberKeepRules, rule, clazz.type);
-            markMatchingFields(clazz, memberKeepRules, rule, clazz.type);
+            // If we're handling -if consequent part, that means precondition already met.
+            DexType precondition = ifRule != null ? null : clazz.type;
+            markMatchingVisibleMethods(clazz, memberKeepRules, rule, precondition);
+            markMatchingFields(clazz, memberKeepRules, rule, precondition);
             break;
           }
           case KEEP_CLASSES_WITH_MEMBERS: {
@@ -191,6 +220,9 @@
             markMatchingFields(clazz, memberKeepRules, rule, null);
             break;
           }
+          case CONDITIONAL:
+            assert rule instanceof ProguardIfRule;
+            throw new Unreachable("-if rule will be evaluated separately, not here.");
         }
       } else if (rule instanceof ProguardCheckDiscardRule) {
         if (memberKeepRules.isEmpty()) {
@@ -220,6 +252,36 @@
     }
   }
 
+  private void runPerRule(
+      ExecutorService executorService,
+      List<Future<?>> futures,
+      ProguardConfigurationRule rule,
+      ProguardIfRule ifRule) {
+    List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
+    if (specifics != null) {
+      // This keep rule only lists specific type matches.
+      // This means there is no need to iterate over all classes.
+      for (DexType type : specifics) {
+        DexClass clazz = application.definitionFor(type);
+        // Ignore keep rule iff it does not reference a class in the app.
+        if (clazz != null) {
+          process(clazz, rule, ifRule);
+        }
+      }
+    } else {
+      futures.add(executorService.submit(() -> {
+        for (DexProgramClass clazz : application.classes()) {
+          process(clazz, rule, ifRule);
+        }
+        if (rule.applyToLibraryClasses()) {
+          for (DexLibraryClass clazz : application.libraryClasses()) {
+            process(clazz, rule, ifRule);
+          }
+        }
+      }));
+    }
+  }
+
   public RootSet run(ExecutorService executorService) throws ExecutionException {
     application.timing.begin("Build root set...");
     try {
@@ -227,28 +289,11 @@
       // Mark all the things explicitly listed in keep rules.
       if (rules != null) {
         for (ProguardConfigurationRule rule : rules) {
-          List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
-          if (specifics != null) {
-            // This keep rule only lists specific type matches.
-            // This means there is no need to iterate over all classes.
-            for (DexType type : specifics) {
-              DexClass clazz = application.definitionFor(type);
-              // Ignore keep rule iff it does not reference a class in the app.
-              if (clazz != null) {
-                process(clazz, rule);
-              }
-            }
+          if (rule instanceof ProguardIfRule) {
+            ProguardIfRule ifRule = (ProguardIfRule) rule;
+            ifRules.add(ifRule);
           } else {
-            futures.add(executorService.submit(() -> {
-              for (DexProgramClass clazz : application.classes()) {
-                process(clazz, rule);
-              }
-              if (rule.applyToLibraryClasses()) {
-                for (DexLibraryClass clazz : application.libraryClasses()) {
-                  process(clazz, rule);
-                }
-              }
-            }));
+            runPerRule(executorService, futures, rule, null);
           }
         }
         ThreadUtils.awaitFutures(futures);
@@ -267,33 +312,65 @@
         noSideEffects,
         assumedValues,
         dependentNoShrinking,
-        identifierNameStrings);
+        identifierNameStrings,
+        ifRules);
   }
 
-  private void markMatchingVisibleMethods(DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+  ConsequentRootSet runForIfRules(
+      ExecutorService executorService,
+      Set<DexType> liveTypes,
+      Set<DexEncodedMethod> liveMethods,
+      Set<DexEncodedField> liveFields) throws ExecutionException {
+    application.timing.begin("Find consequent items for -if rules...");
+    try {
+      List<Future<?>> futures = new ArrayList<>();
+      if (rules != null) {
+        for (ProguardConfigurationRule rule : rules) {
+          assert rule instanceof ProguardIfRule;
+          ProguardIfRule ifRule = (ProguardIfRule) rule;
+          if (ruleSatisfiedWithLiveItems(
+              ifRule, appInfo::definitionFor, liveTypes, liveMethods, liveFields)) {
+            runPerRule(executorService, futures, ifRule.subsequentRule, ifRule);
+          }
+        }
+        ThreadUtils.awaitFutures(futures);
+      }
+    } finally {
+      application.timing.end();
+    }
+    return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation);
+  }
+
+  private void markMatchingVisibleMethods(
+      DexClass clazz,
+      Collection<ProguardMemberRule> memberKeepRules,
+      ProguardConfigurationRule rule,
       DexType onlyIfClassKept) {
     Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>();
     Arrays.stream(clazz.directMethods()).forEach(method ->
-        markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
+        markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
     while (clazz != null) {
       Arrays.stream(clazz.virtualMethods()).forEach(method ->
-          markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
+          markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
       clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
     }
   }
 
-  private void markMatchingMethods(DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+  private void markMatchingMethods(
+      DexClass clazz,
+      Collection<ProguardMemberRule> memberKeepRules,
+      ProguardConfigurationRule rule,
       DexType onlyIfClassKept) {
     Arrays.stream(clazz.directMethods()).forEach(method ->
-        markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+        markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
     Arrays.stream(clazz.virtualMethods()).forEach(method ->
-        markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+        markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
   }
 
-  private void markMatchingFields(DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+  private void markMatchingFields(
+      DexClass clazz,
+      Collection<ProguardMemberRule> memberKeepRules,
+      ProguardConfigurationRule rule,
       DexType onlyIfClassKept) {
     clazz.forEachField(field -> markField(field, memberKeepRules, rule, onlyIfClassKept));
   }
@@ -341,6 +418,79 @@
     out.close();
   }
 
+  private boolean satisfyInheritanceRule(
+      DexType type,
+      Function<DexType, DexClass> definitionFor,
+      ProguardConfigurationRule rule) {
+    DexClass clazz = definitionFor.apply(type);
+    if (clazz == null) {
+      return false;
+    }
+    return
+        anySuperTypeMatches(
+            clazz.superType,
+            definitionFor,
+            rule.getInheritanceClassName(),
+            rule.getInheritanceAnnotation())
+        || anyImplementedInterfaceMatches(
+            clazz,
+            definitionFor,
+            rule.getInheritanceClassName(),
+            rule.getInheritanceAnnotation());
+  }
+
+  private boolean ruleSatisfiedWithLiveItems(
+      ProguardIfRule ifRule,
+      Function<DexType, DexClass> definitionFor,
+      Set<DexType> liveTypes,
+      Set<DexEncodedMethod> liveMethods,
+      Set<DexEncodedField> liveFields) {
+    DexType matchedType = null;
+    Function<DexType, DexClass> definitionForWithLiveTypes = type -> {
+      DexClass clazz = definitionFor.apply(type);
+      if (clazz != null && liveTypes.contains(clazz.type)) {
+        return clazz;
+      }
+      return null;
+    };
+    for (DexType liveType : liveTypes) {
+      if (ifRule.hasInheritanceClassName()) {
+        if (!satisfyInheritanceRule(liveType, definitionForWithLiveTypes, ifRule)) {
+          // Try another live type since the current one doesn't satisfy the inheritance rule.
+          continue;
+        }
+      }
+      if (ifRule.getClassNames().matches(liveType)) {
+        matchedType = liveType;
+        final DexType filterType = matchedType;
+        Collection<ProguardMemberRule> memberKeepRules = ifRule.getMemberRules();
+        Set<DexEncodedMethod> filteredMethods =
+            liveMethods.stream().filter(method -> method.method.getHolder() == filterType)
+                .collect(Collectors.toSet());
+        Set<DexEncodedField> filteredFields =
+            liveFields.stream().filter(field -> field.field.getHolder() == filterType)
+                .collect(Collectors.toSet());
+        boolean notSatisfied = false;
+        for (ProguardMemberRule rule : memberKeepRules) {
+          if (!ruleSatisfiedByMethods(rule, filteredMethods)
+            && !ruleSatisfiedByFields(rule, filteredFields)) {
+            notSatisfied = true;
+            break;
+          }
+        }
+        // Try another live type if the current live type does not satisfy any member rule.
+        if (notSatisfied) {
+          continue;
+        }
+        // TODO(b/73800755): we may need to return the matched live type for back reference.
+        // Maybe, collect all matched live types.
+        return true;
+      }
+    }
+    // No live types satisfy the given -if rule.
+    return false;
+  }
+
   private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
       DexClass clazz) {
     for (ProguardMemberRule rule : memberKeepRules) {
@@ -362,10 +512,36 @@
         || ruleSatisfiedByFields(rule, clazz.instanceFields());
   }
 
+  private boolean ruleSatisfiedByMethods(
+      ProguardMemberRule rule,
+      Iterable<DexEncodedMethod> methods) {
+    if (rule.getRuleType().includesMethods()) {
+      for (DexEncodedMethod method : methods) {
+        if (rule.matches(method, dexStringCache)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   private boolean ruleSatisfiedByMethods(ProguardMemberRule rule, DexEncodedMethod[] methods) {
     if (rule.getRuleType().includesMethods()) {
       for (DexEncodedMethod method : methods) {
-        if (rule.matches(method, this)) {
+        if (rule.matches(method, dexStringCache)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean ruleSatisfiedByFields(
+      ProguardMemberRule rule,
+      Iterable<DexEncodedField> fields) {
+    if (rule.getRuleType().includesFields()) {
+      for (DexEncodedField field : fields) {
+        if (rule.matches(field, dexStringCache)) {
           return true;
         }
       }
@@ -376,7 +552,7 @@
   private boolean ruleSatisfiedByFields(ProguardMemberRule rule, DexEncodedField[] fields) {
     if (rule.getRuleType().includesFields()) {
       for (DexEncodedField field : fields) {
-        if (rule.matches(field, this)) {
+        if (rule.matches(field, dexStringCache)) {
           return true;
         }
       }
@@ -400,21 +576,18 @@
     return false;
   }
 
-  private final ConcurrentHashMap<DexString, String> stringCache = new ConcurrentHashMap<>();
-
-  public String lookupString(DexString name) {
-    return stringCache.computeIfAbsent(name, DexString::toString);
-  }
-
-  private void markMethod(DexEncodedMethod method, Collection<ProguardMemberRule> rules,
-      ProguardConfigurationRule context, Set<Wrapper<DexMethod>> methodsMarked,
-      DexType onlyIfClassKept) {
+  private void markMethod(
+      DexEncodedMethod method,
+      Collection<ProguardMemberRule> rules,
+      Set<Wrapper<DexMethod>> methodsMarked,
+      ProguardConfigurationRule context,
+      DexItem precondition) {
     if ((methodsMarked != null)
         && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
       return;
     }
     for (ProguardMemberRule rule : rules) {
-      if (rule.matches(method, this)) {
+      if (rule.matches(method, dexStringCache)) {
         if (Log.ENABLED) {
           Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
               rule);
@@ -422,20 +595,23 @@
         if (methodsMarked != null) {
           methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
         }
-        addItemToSets(method, context, rule, onlyIfClassKept);
+        addItemToSets(method, context, rule, precondition);
       }
     }
   }
 
-  private void markField(DexEncodedField field, Collection<ProguardMemberRule> rules,
-      ProguardConfigurationRule context, DexType onlyIfClassKept) {
+  private void markField(
+      DexEncodedField field,
+      Collection<ProguardMemberRule> rules,
+      ProguardConfigurationRule context,
+      DexItem precondition) {
     for (ProguardMemberRule rule : rules) {
-      if (rule.matches(field, this)) {
+      if (rule.matches(field, dexStringCache)) {
         if (Log.ENABLED) {
           Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context,
               rule);
         }
-        addItemToSets(field, context, rule, onlyIfClassKept);
+        addItemToSets(field, context, rule, precondition);
       }
     }
   }
@@ -480,14 +656,17 @@
     }
   }
 
-  private synchronized void addItemToSets(DexItem item, ProguardConfigurationRule context,
-      ProguardMemberRule rule, DexType onlyIfClassKept) {
+  private synchronized void addItemToSets(
+      DexItem item,
+      ProguardConfigurationRule context,
+      ProguardMemberRule rule,
+      DexItem precondition) {
     if (context instanceof ProguardKeepRule) {
       ProguardKeepRule keepRule = (ProguardKeepRule) context;
       ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
       if (!modifiers.allowsShrinking) {
-        if (onlyIfClassKept != null) {
-          dependentNoShrinking.computeIfAbsent(onlyIfClassKept, x -> new IdentityHashMap<>())
+        if (precondition != null) {
+          dependentNoShrinking.computeIfAbsent(precondition, x -> new IdentityHashMap<>())
               .put(item, keepRule);
         } else {
           noShrinking.put(item, keepRule);
@@ -540,6 +719,7 @@
     public final Map<DexItem, ProguardMemberRule> assumedValues;
     private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
     public final Set<DexItem> identifierNameStrings;
+    public final Set<ProguardIfRule> ifRules;
 
     private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
       assert item instanceof DexType
@@ -573,10 +753,11 @@
         Map<DexItem, ProguardMemberRule> noSideEffects,
         Map<DexItem, ProguardMemberRule> assumedValues,
         Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
-        Set<DexItem> identifierNameStrings) {
+        Set<DexItem> identifierNameStrings,
+        Set<ProguardIfRule> ifRules) {
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
-      this.noOptimization = Collections.unmodifiableSet(noOptimization);
-      this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
+      this.noOptimization = noOptimization;
+      this.noObfuscation = noObfuscation;
       this.reasonAsked = Collections.unmodifiableSet(reasonAsked);
       this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
       this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
@@ -585,6 +766,7 @@
       this.assumedValues = Collections.unmodifiableMap(assumedValues);
       this.dependentNoShrinking = dependentNoShrinking;
       this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
+      this.ifRules = Collections.unmodifiableSet(ifRules);
       assert legalNoObfuscationItems(noObfuscation);
       assert legalDependentNoShrinkingItems(dependentNoShrinking);
     }
@@ -629,6 +811,7 @@
       builder.append("\nassumedValues: " + assumedValues.size());
       builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
       builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
+      builder.append("\nifRules: " + ifRules.size());
 
       builder.append("\n\nNo Shrinking:");
       noShrinking.keySet().stream()
@@ -639,4 +822,20 @@
       return builder.toString();
     }
   }
+
+  // A partial RootSet that becomes live due to the enabled -if rule.
+  static class ConsequentRootSet {
+    final Map<DexItem, ProguardKeepRule> noShrinking;
+    final Set<DexItem> noOptimization;
+    final Set<DexItem> noObfuscation;
+
+    private ConsequentRootSet(
+        Map<DexItem, ProguardKeepRule> noShrinking,
+        Set<DexItem> noOptimization,
+        Set<DexItem> noObfuscation) {
+      this.noShrinking = Collections.unmodifiableMap(noShrinking);
+      this.noOptimization = Collections.unmodifiableSet(noOptimization);
+      this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 5b1e7e3..b60ede7 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -30,6 +30,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 import org.junit.Before;
 
@@ -74,10 +75,11 @@
       ClassAndMemberPublicizer.run(program, dexItemFactory);
     }
 
-    RootSet rootSet = new RootSetBuilder(program, appInfo, configuration.getRules(), options)
-        .run(ThreadUtils.getExecutorService(options));
+    ExecutorService executor = ThreadUtils.getExecutorService(1);
+    RootSet rootSet = new RootSetBuilder(appInfo, program, configuration.getRules(), options)
+        .run(executor);
     Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
-    appInfo = enqueuer.traceApplication(rootSet, timing);
+    appInfo = enqueuer.traceApplication(rootSet, executor, timing);
     return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index c483267..1631a1f 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -42,11 +42,13 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 import org.junit.Assert;
@@ -103,11 +105,11 @@
     DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
     AppInfoWithSubtyping appInfoWithSubtyping = new AppInfoWithSubtyping(application);
 
-    RootSet rootSet = new RootSetBuilder(application, appInfoWithSubtyping,
-        buildKeepRuleForClass(Main.class, application.dexItemFactory), options).run(
-        Executors.newSingleThreadExecutor());
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    RootSet rootSet = new RootSetBuilder(appInfoWithSubtyping, application,
+        buildKeepRuleForClass(Main.class, application.dexItemFactory), options).run(executor);
     appInfo = new Enqueuer(appInfoWithSubtyping, options, options.forceProguardCompatibility)
-        .traceApplication(rootSet, timing);
+        .traceApplication(rootSet, executor, timing);
     // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
     // due to liveness.
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index f9e0422..5dc4ec6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1163,10 +1163,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    assertEquals(3, handler.warnings.size());
-    for (int i = 0; i < 3; i++) {
-      assertTrue(handler.warnings.get(i).getDiagnosticMessage().contains("Ignoring option: -if"));
-    }
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     // Three -if rules and one independent -keepnames
     assertEquals(4, config.getRules().size());
@@ -1196,8 +1193,7 @@
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(proguardConfig);
-    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
-        "Ignoring", "-if");
+    verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
     assertEquals(1, config.getRules().size());
     ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
index 004c31f..3976f89 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -1,9 +1,12 @@
 // Copyright (c) 2018, 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.forceproguardcompatibility;
 
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
 import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
@@ -12,6 +15,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
@@ -113,4 +117,20 @@
       throws Exception {
     return runProguard6AndD8(programClasses, keepMainProguardConfiguration(mainClass));
   }
+
+  protected void verifyClassesPresent(
+      DexInspector dexInspector, Class<?>... classesOfInterest) {
+    for (Class klass : classesOfInterest) {
+      ClassSubject c = dexInspector.clazz(klass);
+      assertThat(c, isPresent());
+    }
+  }
+
+  protected void verifyClassesAbsent(
+      DexInspector dexInspector, Class<?>... classesOfInterest) {
+    for (Class klass : classesOfInterest) {
+      ClassSubject c = dexInspector.clazz(klass);
+      assertThat(c, not(isPresent()));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
new file mode 100644
index 0000000..080df2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2018, 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.ifrule;
+
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+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 IfOnAnnotationTest extends ProguardCompatabilityTestBase {
+  private final static List<Class> CLASSES = ImmutableList.of(
+      UsedAnnotation.class, UnusedAnnotation.class,
+      UsedAnnotationDependent.class, UnusedAnnotationDependent.class,
+      AnnotationUser.class, MainUsesAnnotationUser.class);
+
+  private final Shrinker shrinker;
+
+  public IfOnAnnotationTest(Shrinker shrinker) {
+    this.shrinker = shrinker;
+  }
+
+  @Parameters(name = "shrinker: {0}")
+  public static Collection<Object> data() {
+    return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8);
+  }
+
+  @Test
+  public void ifOnAnnotation_withoutNthWildcard() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keepattributes *Annotation*",
+        "-keep class **.Main* {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        // @UsedAnnotation <methods> -> UsedAnnotationDependent
+        "-if class **.*User {",
+        "  @**.UsedAnnotation <methods>;",
+        "}",
+        "-keep class **.UsedAnnotation*",
+        // @UnusedAnnotation <methods> -> UnusedAnnotationDependent
+        "-if class **.*User {",
+        "  @**.UnusedAnnotation <methods>;",
+        "}",
+        "-keep class **.UnusedAnnotation*"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    verifyClassesAbsent(dexInspector,
+        UnusedAnnotation.class, UnusedAnnotationDependent.class);
+    verifyClassesPresent(dexInspector,
+        UsedAnnotation.class, UsedAnnotationDependent.class);
+  }
+
+  @Test
+  public void ifOnAnnotation_withNthWildcard() throws Exception {
+    // TODO(b/73800755): not implemented yet.
+    if (shrinker == Shrinker.R8) {
+      return;
+    }
+
+    List<String> config = ImmutableList.of(
+        "-keepattributes *Annotation*",
+        "-keep class **.Main* {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        // @UsedAnnotation <methods> -> UsedAnnotationDependent
+        "-if class **.*User {",
+        "  @<1>.Used<2> <methods>;",
+        "}",
+        "-keep class <1>.Used<2>*",
+        // @UnusedAnnotation <methods> -> UnusedAnnotationDependent
+        "-if class **.*User {",
+        "  @<1>.Unused<2> <methods>;",
+        "}",
+        "-keep class <1>.Unused<2>*"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    verifyClassesAbsent(dexInspector,
+        UnusedAnnotation.class, UnusedAnnotationDependent.class);
+    verifyClassesPresent(dexInspector,
+        UsedAnnotation.class, UsedAnnotationDependent.class);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTestClasses.java
new file mode 100644
index 0000000..56fb4c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTestClasses.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, 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.ifrule;
+
+@interface UsedAnnotation {
+  // Intentionally left empty.
+}
+
+@interface UnusedAnnotation {
+  // Intentionally left empty.
+}
+
+class UsedAnnotationDependent {
+  // Intentionally left empty.
+}
+
+class UnusedAnnotationDependent {
+  // Intentionally left empty.
+}
+
+class AnnotationUser {
+  private int intField;
+
+  @UsedAnnotation void live() {
+    System.out.println("live(" + intField++ + ")");
+  }
+
+  @UnusedAnnotation void dead() {
+    throw new AssertionError("Should be removed.");
+  }
+}
+
+class MainUsesAnnotationUser {
+  public static void main(String[] args) {
+    AnnotationUser user = new AnnotationUser();
+    user.live();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 5f3cae1..c26f43f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -78,7 +78,44 @@
   }
 
   @Test
-  public void ifThenKeep() throws Exception {
+  public void ifThenKeep_withoutNthWildcard() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-if class **.Precondition",
+        "-keep,allowobfuscation class **.*User",
+        "-if class **.DependentUser",
+        "-keep,allowobfuscation class **.Dependent"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    if (!keepPrecondition) {
+      // TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary.
+      if (shrinker == Shrinker.PROGUARD6) {
+        return;
+      }
+      assertEquals(1, dexInspector.allClasses().size());
+      return;
+    }
+
+    ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+    assertThat(clazz, isRenamed());
+    // Members of DependentUser are not used anywhere.
+    MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
+    assertThat(m, not(isPresent()));
+    FieldSubject f = clazz.field("int", "canBeShrinked");
+    assertThat(f, not(isPresent()));
+
+    // Although DependentUser#callFoo is shrinked, Dependent is kept via -if.
+    clazz = dexInspector.clazz(Dependent.class);
+    assertThat(clazz, isRenamed());
+    // But, its members are gone.
+    m = clazz.method("java.lang.String", "foo", ImmutableList.of());
+    assertThat(m, not(isPresent()));
+    f = clazz.field("int", "intField");
+    assertThat(f, not(isPresent()));
+  }
+
+  @Test
+  public void ifThenKeep_withNthWildcard() throws Exception {
     List<String> config = ImmutableList.of(
         "-if class **.Precondition",
         "-keep,allowobfuscation class **.*User",
@@ -96,7 +133,7 @@
       return;
     }
 
-    // TODO(b/73708139): not implemented yet.
+    // TODO(b/73800755): not implemented yet.
     if (shrinker == Shrinker.R8) {
       return;
     }
@@ -134,8 +171,43 @@
       return;
     }
 
-    // TODO(b/73708139): not implemented yet.
-    if (shrinker == Shrinker.R8) {
+    ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+    assertThat(clazz, isRenamed());
+    MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
+    assertThat(m, isRenamed());
+    FieldSubject f = clazz.field("int", "canBeShrinked");
+    assertThat(f, not(isPresent()));
+
+    // Dependent is kept due to DependentUser#callFoo, but renamed.
+    clazz = dexInspector.clazz(Dependent.class);
+    assertThat(clazz, isRenamed());
+    m = clazz.method("java.lang.String", "foo", ImmutableList.of());
+    assertThat(m, isRenamed());
+    f = clazz.field("int", "intField");
+    assertThat(f, isRenamed());
+  }
+
+  @Test
+  public void ifThenKeepClassMembers_withoutNthWildcard() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-if class **.Precondition",
+        "-keepclassmembers,allowobfuscation class **.*User {",
+        "  static void callFoo(...);",
+        "}",
+        // If the member is kept, keep the enclosing class too.
+        "-if class **.*User {",
+        "  static void callFoo(...);",
+        "}",
+        "-keep,allowobfuscation class **.*User"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    if (!keepPrecondition) {
+      // TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary.
+      if (shrinker == Shrinker.PROGUARD6) {
+        return;
+      }
+      assertEquals(1, dexInspector.allClasses().size());
       return;
     }
 
@@ -156,15 +228,15 @@
   }
 
   @Test
-  public void ifThenKeepClassMembers() throws Exception {
+  public void ifThenKeepClassMembers_withNthWildcard() throws Exception {
     List<String> config = ImmutableList.of(
         "-if class **.Precondition",
         "-keepclassmembers,allowobfuscation class **.*User {",
         "  static void callFoo(...);",
         "}",
-        // If members are kept, keep the enclosing class too.
+        // If the member is kept, keep the enclosing class too.
         "-if class **.*User {",
-        "  <methods>;",
+        "  static void callFoo(...);",
         "}",
         "-keep,allowobfuscation class <1>.<2>User"
     );
@@ -179,7 +251,7 @@
       return;
     }
 
-    // TODO(b/73708139): not implemented yet.
+    // TODO(b/73800755): not implemented yet.
     if (shrinker == Shrinker.R8) {
       return;
     }
@@ -214,11 +286,6 @@
 
     DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
 
-    // TODO(b/73708139): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     ClassSubject clazz = dexInspector.clazz(Dependent.class);
     // Only class name is not renamed, if triggered.
     assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
@@ -244,11 +311,6 @@
 
     DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
 
-    // TODO(b/73708139): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     ClassSubject clazz = dexInspector.clazz(Dependent.class);
     // Class name is not renamed, if triggered.
     assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
@@ -275,11 +337,6 @@
 
     DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
 
-    // TODO(b/73708139): not implemented yet.
-    if (shrinker == Shrinker.R8) {
-      return;
-    }
-
     ClassSubject clazz = dexInspector.clazz(Dependent.class);
     assertThat(clazz, isRenamed());
     MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 07e94cd..2bf8a27 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -3,15 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.ifrule;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
 import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -27,7 +23,8 @@
       D.class, D1.class, D2.class,
       R.class, R1.class, R2.class,
       MainWithInner.InnerR.class, MainWithInner.InnerD.class,
-      MainUsesR.class, MainWithIf.class, MainWithInner.class);
+      I.class, Impl.class,
+      MainUsesR.class, MainWithIf.class, MainWithInner.class, MainUsesImpl.class);
 
   private final Shrinker shrinker;
 
@@ -59,24 +56,39 @@
     return super.runProguard6(programClasses, adaptConfiguration(proguardConfig));
   }
 
-  private void verifyClassesPresent(
-      DexInspector dexInspector, Class<?>... classesOfInterest) {
-    for (Class klass : classesOfInterest) {
-      ClassSubject c = dexInspector.clazz(klass);
-      assertThat(c, isPresent());
-    }
-  }
+  @Test
+  public void ifOnField_withoutNthWildcard() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **.MainUsesR {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        // R.id1 -> D1
+        "-if class **.R {",
+        "  public static int id1;",
+        "}",
+        "-keep class **.D1",
+        // R.id2 -> D2
+        "-if class **.R {",
+        "  public static int id2;",
+        "}",
+        "-keep class **.D2",
+        // R.id1 && R.id2 -> D
+        "-if class **.R {",
+        "  public static int id1;",
+        "  public static int id2;",
+        "}",
+        "-keep class **.D"
+    );
 
-  private void verifyClassesAbsent(
-      DexInspector dexInspector, Class<?>... classesOfInterest) {
-    for (Class klass : classesOfInterest) {
-      ClassSubject c = dexInspector.clazz(klass);
-      assertThat(c, not(isPresent()));
-    }
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    verifyClassesAbsent(dexInspector,
+        R1.class, R2.class, D.class, D2.class);
+    verifyClassesPresent(dexInspector,
+        R.class, D1.class);
   }
 
   @Test
-  public void ifOnField() throws Exception {
+  public void ifOnField_withNthWildcard() throws Exception {
     // TODO(b/73800755): not implemented yet.
     if (shrinker == Shrinker.R8) {
       return;
@@ -87,13 +99,9 @@
         "  public static void main(java.lang.String[]);",
         "}",
         "-if class **.R {",
-        "  public static int id1;",
+        "  public static int id?;",
         "}",
-        "-keep class **.D1",
-        "-if class **.R {",
-        "  public static int id2;",
-        "}",
-        "-keep class **.D2"
+        "-keep class **.D<2>"
     );
 
     DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
@@ -101,26 +109,33 @@
         R1.class, R2.class, D.class, D2.class);
     verifyClassesPresent(dexInspector,
         R.class, D1.class);
-
-    config = ImmutableList.of(
-        "-keep class **.MainUsesR {",
-        "  public static void main(java.lang.String[]);",
-        "}",
-        "-if class **.R {",
-        "  public static int id?;",
-        "}",
-        "-keep class **.D<2>"
-    );
-
-    dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
-        R1.class, R2.class, D.class, D2.class);
-    verifyClassesPresent(dexInspector,
-        R.class, D1.class);
   }
 
   @Test
-  public void ifOnFieldWithCapture() throws Exception {
+  public void ifOnFieldWithCapture_withoutNthWildcard() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **.MainWithIf {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        "-if class **.R1 {",
+        "  public static int id*;",
+        "}",
+        "-keep class **.D1",
+        "-if class **.R2 {",
+        "  public static int id*;",
+        "}",
+        "-keep class **.D2"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    verifyClassesAbsent(dexInspector,
+        R.class, D.class, R1.class, D1.class);
+    verifyClassesPresent(dexInspector,
+        R2.class, D2.class);
+  }
+
+  @Test
+  public void ifOnFieldWithCapture_withNthWildcard() throws Exception {
     // TODO(b/73800755): not implemented yet.
     if (shrinker == Shrinker.R8) {
       return;
@@ -131,8 +146,7 @@
         "  public static void main(java.lang.String[]);",
         "}",
         "-if class **.R* {",
-        "  public static int id1;",
-        "  public static int id2;",
+        "  public static int id*;",
         "}",
         "-keep class **.D<2>"
     );
@@ -145,7 +159,27 @@
   }
 
   @Test
-  public void ifOnFieldWithInner() throws Exception {
+  public void ifOnFieldWithInner_withoutNthWildcard() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **.MainWithInner {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        "-if class **$*R {",
+        "  public static int id1;",
+        "  public static int id2;",
+        "}",
+        "-keep class **$*D"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    verifyClassesAbsent(dexInspector,
+        R.class, D.class, R1.class, D1.class, R2.class, D2.class);
+    verifyClassesPresent(dexInspector,
+        MainWithInner.InnerR.class, MainWithInner.InnerD.class);
+  }
+
+  @Test
+  public void ifOnFieldWithInner_withNthWildcard() throws Exception {
     // TODO(b/73800755): not implemented yet.
     if (shrinker == Shrinker.R8) {
       return;
@@ -196,4 +230,53 @@
     }
   }
 
+  @Test
+  public void ifOnFieldInImplementer_withoutNthWildcard() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **.MainUsesImpl {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        "-if class ** implements **.I {",
+        "  private <fields>;",
+        "}",
+        "-keep class **.D1",
+        "-if class ** implements **.I {",
+        "  public <fields>;",
+        "}",
+        "-keep class **.D2"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    verifyClassesAbsent(dexInspector, D2.class);
+    verifyClassesPresent(dexInspector,
+        I.class, Impl.class, D1.class);
+  }
+
+  @Test
+  public void ifOnFieldInImplementer_withNthWildcard() throws Exception {
+    // TODO(b/73800755): not implemented yet.
+    if (shrinker == Shrinker.R8) {
+      return;
+    }
+
+    List<String> config = ImmutableList.of(
+        "-keep class **.MainUsesImpl {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        "-if class ** implements **.I {",
+        "  private <fields>;",
+        "}",
+        "-keep class <2>.D1",
+        "-if class ** implements **.I {",
+        "  public <fields>;",
+        "}",
+        "-keep class <2>.D2"
+    );
+
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    verifyClassesAbsent(dexInspector, D2.class);
+    verifyClassesPresent(dexInspector,
+        I.class, Impl.class, D1.class);
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java
index 892b63e..bd8e5c2 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java
@@ -27,6 +27,21 @@
   public static int id2 = 2;
 }
 
+interface I {
+  void ack();
+}
+
+class Impl implements I {
+  private int usedPrivateIntField;
+  private int unusedPrivateIntField;
+  public int unusedPublicIntField;
+  public String unusedPublicStringField;
+
+  public void ack() {
+    System.out.println("ack(" + usedPrivateIntField++ + ")");
+  }
+}
+
 class MainUsesR {
   public static void main(String[] args) {
     System.out.println(R.id1);
@@ -62,3 +77,9 @@
   }
 }
 
+class MainUsesImpl {
+  public static void main(String[] args) {
+    I instance = new Impl();
+    instance.ack();
+  }
+}