Merge "Preserve IllegalAccessError during check-cast."
diff --git a/build.gradle b/build.gradle
index 7a44d6d..eed9769 100644
--- a/build.gradle
+++ b/build.gradle
@@ -342,6 +342,7 @@
         "youtube/youtube.android_12.10",
         "youtube/youtube.android_12.17",
         "youtube/youtube.android_12.22",
+        "youtube/youtube.android_13.37",
         "proguardsettings",
         "proguard/proguard_internal_159423826",
         "framework",
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 100105f..6fd04b6 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -121,7 +121,7 @@
             "Failed to read dex/vdex file `" + programFile + "`: '" + e.getMessage() + "'");
         continue;
       }
-      System.out.print("In file: " + cwd.relativize(programFile));
+      System.out.print("In file: " + cwd.toAbsolutePath().relativize(programFile.toAbsolutePath()));
       System.out.println(", " + extractDexSize(programFile) + " bytes:");
       for (Marker marker : markers) {
         if (marker == null) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index dc94041..78a9d3f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -362,9 +362,8 @@
         mainDexKeepRules = parser.getConfig().getRules();
       }
 
-      ProguardConfigurationParser parser = new ProguardConfigurationParser(
-          factory, reporter,
-          !allowPartiallyImplementedProguardOptions, allowTestProguardOptions);
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(factory, reporter, allowTestProguardOptions);
       if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
       }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index f594c71..1ca8a58 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -112,7 +112,7 @@
   public DexEncodedMethod lookupStaticTarget(DexMethod method) {
     ResolutionResult resolutionResult = resolveMethod(method.holder, method);
     DexEncodedMethod target = resolutionResult.asSingleTarget();
-    return target == null || target.isStaticMethod() ? target : null;
+    return target == null || target.isStatic() ? target : null;
   }
 
   /**
@@ -145,7 +145,7 @@
     }
     resolutionResult = resolveMethod(contextClass.superType, method);
     DexEncodedMethod target = resolutionResult.asSingleTarget();
-    return target == null || !target.isStaticMethod() ? target : null;
+    return target == null || !target.isStatic() ? target : null;
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index b86c4bd..bbc5922 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -350,6 +350,16 @@
     return null;
   }
 
+  @Override
+  public boolean isStatic() {
+    return accessFlags.isStatic();
+  }
+
+  @Override
+  public boolean isStaticMember() {
+    return false;
+  }
+
   public DexEncodedMethod getClassInitializer() {
     return Arrays.stream(directMethods()).filter(DexEncodedMethod::isClassInitializer).findAny()
         .orElse(null);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index c2ccfe3..9127b94 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -74,4 +74,8 @@
   public static Stream<DexEncodedMethod> filterDexEncodedMethod(Stream<DexDefinition> stream) {
     return filter(stream, DexDefinition::isDexEncodedMethod, DexDefinition::asDexEncodedMethod);
   }
+
+  public abstract boolean isStatic();
+
+  public abstract boolean isStaticMember();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 115e88a..42cc0ce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -78,6 +78,16 @@
     return this;
   }
 
+  @Override
+  public boolean isStatic() {
+    return accessFlags.isStatic();
+  }
+
+  @Override
+  public boolean isStaticMember() {
+    return isStatic();
+  }
+
   public boolean hasAnnotation() {
     return !annotations.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 5af5739..3d83104 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -213,14 +213,18 @@
     return (accessFlags.isPrivate() || accessFlags.isConstructor()) && !accessFlags.isStatic();
   }
 
-  /**
-   * Returns true if this method can be invoked via invoke-static.
-   */
-  public boolean isStaticMethod() {
+  @Override
+  public boolean isStatic() {
     checkIfObsolete();
     return accessFlags.isStatic();
   }
 
+  @Override
+  public boolean isStaticMember() {
+    checkIfObsolete();
+    return isStatic();
+  }
+
   /**
    * Returns true if this method is synthetic.
    */
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index f974aca..0a1ee94 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.graph.JarClassFileReader.ReparseContext;
 import com.android.tools.r8.ir.code.IRCode;
@@ -241,7 +242,12 @@
 
   private void parseCode(ReparseContext context, boolean useJsrInliner) {
     SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
-    new ClassReader(context.classCache).accept(classVisitor, ClassReader.SKIP_FRAMES);
+    try {
+      new ClassReader(context.classCache).accept(classVisitor, ClassReader.SKIP_FRAMES);
+    } catch (Exception exception) {
+      throw new CompilationError(
+          "Unable to parse method `" + method.toSourceString() + "`", exception);
+    }
   }
 
   protected BiFunction<String, String, JarCode> createCodeLocator(ReparseContext context) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 19bf655..96cc33a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -688,27 +688,17 @@
   }
 
   public ConstNumber createIntConstant(int value) {
-    return new ConstNumber(createValue(ValueType.INT), value);
-  }
-
-  public ConstNumber createTrue() {
-    return new ConstNumber(createValue(ValueType.INT), 1);
-  }
-
-  public ConstNumber createFalse() {
-    return new ConstNumber(createValue(ValueType.INT), 0);
+    Value out = createValue(ValueType.INT);
+    return new ConstNumber(out, value);
   }
 
   public final int getHighestBlockNumber() {
     return blocks.stream().max(Comparator.comparingInt(BasicBlock::getNumber)).get().getNumber();
   }
 
-  public Instruction createConstNull(Instruction from) {
-    return new ConstNumber(createValue(from.outType()), 0);
-  }
-
   public ConstNumber createConstNull() {
-    return new ConstNumber(createValue(ValueType.OBJECT), 0);
+    Value out = createValue(ValueType.OBJECT);
+    return new ConstNumber(out, 0);
   }
 
   public boolean doAllThrowingInstructionsHavePositions() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index e16e2d5..6836ad3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -130,7 +130,7 @@
 
   private boolean isInstanceMethod(DexEncodedMethod encodedMethod) {
     assert encodedMethod != null;
-    return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.isStaticMethod();
+    return !encodedMethod.accessFlags.isConstructor() && !encodedMethod.isStatic();
   }
 
   private boolean isPrivateInstanceMethod(DexEncodedMethod encodedMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 9541aef..2fddda3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -768,7 +768,7 @@
 
   public void identifyInvokeSemanticsForInlining(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    if (method.isStaticMethod()) {
+    if (method.isStatic()) {
       // Identifies if the method preserves class initialization after inlining.
       feedback.markTriggerClassInitBeforeAnySideEffect(method,
           triggersClassInitializationBeforeSideEffect(code, method.method.getHolder()));
@@ -1335,7 +1335,7 @@
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
-          iterator.replaceCurrentInstruction(code.createFalse());
+          iterator.replaceCurrentInstruction(code.createIntConstant(0));
         }
       } else if (current.isStaticPut()) {
         StaticPut staticPut = current.asStaticPut();
@@ -1345,7 +1345,7 @@
       } else if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
         if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
-          iterator.replaceCurrentInstruction(code.createTrue());
+          iterator.replaceCurrentInstruction(code.createIntConstant(1));
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index bd3acb6..49c740c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -97,7 +97,7 @@
       clazz.forEachMethod(
           encodedMethod -> {
             // We will add private instance methods when we promote them.
-            if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
+            if (!encodedMethod.isPrivateMethod() || encodedMethod.isStatic()) {
               methodPool.seen(equivalence.wrap(encodedMethod.method));
             }
           });
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 42e0bb1..605fdf9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -155,7 +155,7 @@
             method.accessFlags, CLASS_INITIALIZER_FLAGS);
         checkDirectMethodAnnotations(method);
 
-      } else if (method.isStaticMethod()) {
+      } else if (method.isStatic()) {
         throw new LambdaStructureError(
             "unexpected static method: " + method.method.toSourceString());
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index eaa5037..e33d977 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -180,7 +180,7 @@
       return true;
     }
     for (DexEncodedMethod method : clazz.methods()) {
-      if (!method.isStaticMethod() && appInfo.isPinned(method.method)) {
+      if (!method.isStatic() && appInfo.isPinned(method.method)) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 5b930fe..b7c830f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -130,7 +130,7 @@
 
       // CHECK: no abstract or native instance methods.
       if (Streams.stream(candidateClass.methods()).anyMatch(
-          method -> !method.isStaticMethod() && (method.shouldNotHaveCode()))) {
+          method -> !method.isStatic() && (method.shouldNotHaveCode()))) {
         it.remove();
         continue;
       }
@@ -151,7 +151,7 @@
 
       // Collect instance methods to be staticized.
       for (DexEncodedMethod method : candidateClass.methods()) {
-        if (!method.isStaticMethod()) {
+        if (!method.isStatic()) {
           removedInstanceMethods.add(method);
           if (!factory().isConstructor(method.method)) {
             methodsToBeStaticized.add(method);
@@ -334,7 +334,7 @@
       // Move instance methods into static ones.
       List<DexEncodedMethod> newDirectMethods = new ArrayList<>();
       for (DexEncodedMethod method : candidateClass.methods()) {
-        if (method.isStaticMethod()) {
+        if (method.isStatic()) {
           newDirectMethods.add(method);
         } else if (!factory().isConstructor(method.method)) {
           DexEncodedMethod staticizedMethod = method.toStaticMethodWithoutThis();
@@ -374,7 +374,7 @@
   }
 
   private boolean classMembersConflict(DexClass a, DexClass b) {
-    assert Streams.stream(a.methods()).allMatch(DexEncodedMethod::isStaticMethod);
+    assert Streams.stream(a.methods()).allMatch(DexEncodedMethod::isStatic);
     assert a.instanceFields().length == 0;
     return Stream.of(a.staticFields()).anyMatch(fld -> b.lookupField(fld.field) != null) ||
         Streams.stream(a.methods()).anyMatch(method -> b.lookupMethod(method.method) != null);
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 afccf95..7a0aacb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -272,6 +272,24 @@
     }
   }
 
+  private void enqueueHolderIfDependentNonStaticMember(
+      DexClass holder, Map<DexDefinition, ProguardKeepRule> dependentItems) {
+    // Check if any dependent members are not static, and in that case enqueue the class as well.
+    // Having a dependent rule like -keepclassmembers with non static items indicates that class
+    // instances will be present even if tracing do not find any instantiation. See b/115867670.
+    for (Entry<DexDefinition, ProguardKeepRule> entry : dependentItems.entrySet()) {
+      DexDefinition dependentItem = entry.getKey();
+      if (dependentItem.isDexClass()) {
+        continue;
+      }
+      if (!dependentItem.isStaticMember()) {
+        enqueueRootItem(holder, entry.getValue());
+        // Enough to enqueue the known holder once.
+        break;
+      }
+    }
+  }
+
   //
   // Things to do with registering events. This is essentially the interface for byte-code
   // traversals.
@@ -633,16 +651,6 @@
   // Actual actions performed.
   //
 
-  static private boolean isStaticMember(DexDefinition definition) {
-    if (definition.isDexEncodedMethod()) {
-      return (definition.asDexEncodedMethod()).accessFlags.isStatic();
-    }
-    if (definition.isDexEncodedField()) {
-      return (definition.asDexEncodedField()).accessFlags.isStatic();
-    }
-    return false;
-  }
-
   private void markTypeAsLive(DexType type) {
     assert type.isClassType();
     if (liveTypes.add(type)) {
@@ -686,16 +694,8 @@
         annotations.forEach(this::handleAnnotationOfLiveType);
       }
 
-      // Check if any dependent members are not static, and in that case enqueue the class as well.
-      // Having a dependent rule like -keepclassmembers with non static items indicates that class
-      // instances will be present even if tracing do not find any instantiation. See b/115867670.
       Map<DexDefinition, ProguardKeepRule> dependentItems = rootSet.getDependentItems(holder);
-      for (Entry<DexDefinition, ProguardKeepRule> entry : dependentItems.entrySet()) {
-        if (!isStaticMember(entry.getKey())) {
-          enqueueRootItem(holder, entry.getValue());
-          break;
-        }
-      }
+      enqueueHolderIfDependentNonStaticMember(holder, dependentItems);
       // Add all dependent members to the workqueue.
       enqueueRootItems(dependentItems);
     }
@@ -1298,6 +1298,16 @@
           enqueueRootItems(consequentRootSet.noShrinking);
           rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
           rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
+          rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
+          // Check if any newly dependent members are not static, and in that case find the holder
+          // and enqueue it as well. This is -if version of workaround for b/115867670.
+          consequentRootSet.dependentNoShrinking.forEach((precondition, dependentItems) -> {
+            if (precondition.isDexClass()) {
+              enqueueHolderIfDependentNonStaticMember(precondition.asDexClass(), dependentItems);
+            }
+            // Add all dependent members to the workqueue.
+            enqueueRootItems(dependentItems);
+          });
           if (!workList.isEmpty()) {
             continue;
           }
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 a49c15c..faf5317 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -49,7 +49,6 @@
   private final DexItemFactory dexItemFactory;
 
   private final Reporter reporter;
-  private final boolean failOnPartiallyImplementedOptions;
   private final boolean allowTestOptions;
 
   private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
@@ -102,17 +101,15 @@
 
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory, Reporter reporter) {
-    this(dexItemFactory, reporter, true, false);
+    this(dexItemFactory, reporter, false);
   }
 
   public ProguardConfigurationParser(
-      DexItemFactory dexItemFactory, Reporter reporter, boolean failOnPartiallyImplementedOptions,
-      boolean allowTestOptions) {
+      DexItemFactory dexItemFactory, Reporter reporter, boolean allowTestOptions) {
     this.dexItemFactory = dexItemFactory;
     configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
 
     this.reporter = reporter;
-    this.failOnPartiallyImplementedOptions = failOnPartiallyImplementedOptions;
     this.allowTestOptions = allowTestOptions;
   }
 
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 54a3f2f..6d6d943 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -72,7 +72,7 @@
         Origin.unknown(),
         Position.UNKNOWN,
         null,
-        getClassAnnotation(),
+        getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
         getClassTypeNegated(),
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 3c1fb2d..dd2bd33 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -63,7 +63,7 @@
         Origin.unknown(),
         Position.UNKNOWN,
         null,
-        getClassAnnotation(),
+        getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
         getClassTypeNegated(),
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 43227d7..30def9e 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
@@ -34,10 +35,12 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -148,16 +151,13 @@
       DexClass clazz,
       ProguardConfigurationRule rule,
       ProguardIfRule ifRule) {
-    if (rule.getClassType().matches(clazz) == rule.getClassTypeNegated()) {
+    if (!satisfyClassType(rule, clazz)) {
       return;
     }
-    if (!rule.getClassAccessFlags().containsAll(clazz.accessFlags)) {
+    if (!satisfyAccessFlag(rule, clazz)) {
       return;
     }
-    if (!rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags)) {
-      return;
-    }
-    if (!containsAnnotation(rule.getClassAnnotation(), clazz.annotations)) {
+    if (!satisfyAnnotation(rule, clazz)) {
       return;
     }
     // In principle it should make a difference whether the user specified in a class
@@ -202,13 +202,14 @@
 
     if (rule.getClassNames().matches(clazz.type)) {
       Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
+      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
       if (rule instanceof ProguardKeepRule) {
         switch (((ProguardKeepRule) rule).getType()) {
           case KEEP_CLASS_MEMBERS: {
-            // If we're handling -if consequent part, that means precondition already met.
-            DexClass precondition = ifRule != null ? null : clazz;
-            markMatchingVisibleMethods(clazz, memberKeepRules, rule, precondition);
-            markMatchingFields(clazz, memberKeepRules, rule, precondition);
+            // Members mentioned at -keepclassmembers always depend on their holder.
+            preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+            markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
+            markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
             break;
           }
           case KEEP_CLASSES_WITH_MEMBERS: {
@@ -219,20 +220,33 @@
           }
           case KEEP: {
             markClass(clazz, rule);
-            markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
-            markMatchingFields(clazz, memberKeepRules, rule, null);
+            preconditionSupplier = new HashMap<>();
+            if (ifRule != null) {
+              // Static members in -keep are pinned no matter what.
+              preconditionSupplier.put(DexDefinition::isStaticMember, null);
+              // Instance members may need to be kept even though the holder is not instantiated.
+              preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
+            } else {
+              // Members mentioned at -keep should always be pinned as long as that -keep rule is
+              // not triggered conditionally.
+              preconditionSupplier.put((definition -> true), null);
+            }
+            markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
+            markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
             break;
           }
           case CONDITIONAL:
-            assert rule instanceof ProguardIfRule;
             throw new Unreachable("-if rule will be evaluated separately, not here.");
         }
+      } else if (rule instanceof ProguardIfRule) {
+        throw new Unreachable("-if rule will be evaluated separately, not here.");
       } else if (rule instanceof ProguardCheckDiscardRule) {
         if (memberKeepRules.isEmpty()) {
           markClass(clazz, rule);
         } else {
-          markMatchingFields(clazz, memberKeepRules, rule, clazz);
-          markMatchingMethods(clazz, memberKeepRules, rule, clazz);
+          preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+          markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
+          markMatchingMethods(clazz, memberKeepRules, rule, preconditionSupplier);
         }
       } else if (rule instanceof ProguardWhyAreYouKeepingRule
           || rule instanceof ProguardKeepPackageNamesRule) {
@@ -344,6 +358,19 @@
           // -keep rule may vary (due to back references). So, we need to try all pairs of -if rule
           // and live types.
           for (DexType currentLiveType : liveTypes) {
+            DexClass currentLiveClass = appInfo.definitionFor(currentLiveType);
+            if (currentLiveClass == null) {
+              continue;
+            }
+            if (!satisfyClassType(rule, currentLiveClass)) {
+              continue;
+            }
+            if (!satisfyAccessFlag(rule, currentLiveClass)) {
+              continue;
+            }
+            if (!satisfyAnnotation(rule, currentLiveClass)) {
+              continue;
+            }
             if (ifRule.hasInheritanceClassName()) {
               if (!satisfyInheritanceRule(currentLiveType, definitionForWithLiveTypes, ifRule)) {
                 // Try another live type since the current one doesn't satisfy the inheritance rule.
@@ -406,20 +433,44 @@
     } finally {
       application.timing.end();
     }
-    return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation);
+    return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+  }
+
+  private static DexDefinition testAndGetPrecondition(
+      DexDefinition definition, Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+    if (preconditionSupplier == null) {
+      return null;
+    }
+    DexDefinition precondition = null;
+    boolean conditionEverMatched = false;
+    for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
+      if (entry.getKey().test(definition)) {
+        precondition = entry.getValue();
+        conditionEverMatched = true;
+        break;
+      }
+    }
+    // If precondition-supplier is given, there should be at least one predicate that holds.
+    // Actually, there should be only one predicate as we break the loop when it is found.
+    assert conditionEverMatched;
+    return precondition;
   }
 
   private void markMatchingVisibleMethods(
       DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
-      DexClass onlyIfClassKept) {
+      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
     Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>();
-    Arrays.stream(clazz.directMethods()).forEach(method ->
-        markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
+    Arrays.stream(clazz.directMethods()).forEach(method -> {
+      DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+      markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
+    });
     while (clazz != null) {
-      Arrays.stream(clazz.virtualMethods()).forEach(method ->
-          markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
+      Arrays.stream(clazz.virtualMethods()).forEach(method -> {
+        DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+        markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
+      });
       clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
     }
   }
@@ -428,19 +479,22 @@
       DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
-      DexClass onlyIfClassKept) {
-    Arrays.stream(clazz.directMethods()).forEach(method ->
-        markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
-    Arrays.stream(clazz.virtualMethods()).forEach(method ->
-        markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
+      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+    clazz.forEachMethod(method -> {
+      DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+      markMethod(method, memberKeepRules, null, rule, precondition);
+    });
   }
 
   private void markMatchingFields(
       DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
-      DexClass onlyIfClassKept) {
-    clazz.forEachField(field -> markField(field, memberKeepRules, rule, onlyIfClassKept));
+      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+    clazz.forEachField(field -> {
+      DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+      markField(field, memberKeepRules, rule, precondition);
+    });
   }
 
   // TODO(67934426): Test this code.
@@ -496,6 +550,19 @@
     out.close();
   }
 
+  private static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
+    return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
+  }
+
+  private static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) {
+    return rule.getClassAccessFlags().containsAll(clazz.accessFlags)
+        && rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
+  }
+
+  private static boolean satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
+    return containsAnnotation(rule.getClassAnnotation(), clazz.annotations);
+  }
+
   private boolean satisfyInheritanceRule(
       DexType type,
       Function<DexType, DexClass> definitionFor,
@@ -794,6 +861,15 @@
       this.ifRules = Collections.unmodifiableSet(ifRules);
     }
 
+    // Add dependent items that depend on -if rules.
+    void addDependentItems(
+        Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentItems) {
+      dependentItems.forEach((def, dependence) -> {
+        dependentNoShrinking.computeIfAbsent(def, x -> new IdentityHashMap<>())
+            .putAll(dependence);
+      });
+    }
+
     Map<DexDefinition, ProguardKeepRule> getDependentItems(DexDefinition item) {
       return Collections
           .unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
@@ -831,14 +907,17 @@
     final Map<DexDefinition, ProguardKeepRule> noShrinking;
     final Set<DexDefinition> noOptimization;
     final Set<DexDefinition> noObfuscation;
+    final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
 
     private ConsequentRootSet(
         Map<DexDefinition, ProguardKeepRule> noShrinking,
         Set<DexDefinition> noOptimization,
-        Set<DexDefinition> noObfuscation) {
+        Set<DexDefinition> noObfuscation,
+        Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = Collections.unmodifiableSet(noOptimization);
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
+      this.dependentNoShrinking = Collections.unmodifiableMap(dependentNoShrinking);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 43f8d20..5e03071 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -811,7 +811,7 @@
           deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
           deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method);
 
-          if (!directMethod.isStaticMethod()) {
+          if (!directMethod.isStatic()) {
             blockRedirectionOfSuperCalls(resultingDirectMethod.method);
           }
         }
@@ -1069,7 +1069,7 @@
       accessFlags.setSynthetic();
       accessFlags.unsetAbstract();
 
-      assert invocationTarget.isPrivateMethod() == !invocationTarget.isStaticMethod();
+      assert invocationTarget.isPrivateMethod() == !invocationTarget.isStatic();
       SynthesizedBridgeCode code =
           new SynthesizedBridgeCode(
               newMethod,
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
index 09a46e5..85749ab 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
@@ -6,6 +6,9 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -14,10 +17,10 @@
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
 
 /**
  * Test to check that locals that are introduced in a block that is not hit, still start in the
@@ -25,6 +28,7 @@
  *
  * <p>See b/75251251 or b/78617758
  */
+@RunWith(VmTestRunner.class)
 public class LocalsLiveAtBlockEntryDebugTest extends DebugTestBase {
 
   final String className = "LocalsLiveAtEntry";
@@ -45,7 +49,7 @@
   }
 
   @Test
-  @Ignore("b/78617758")
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
   public void testD8() throws Throwable {
     JasminBuilder builder = getBuilderForTest(className, methodName);
     List<Path> outputs = builder.writeClassFiles(temp.newFolder().toPath());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 9be118c..c184e6e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -185,7 +185,7 @@
     assertNotNull(clazz);
     assertTrue(clazz.isPresent());
     return Streams.stream(clazz.getDexClass().methods())
-        .filter(method -> !method.isStaticMethod())
+        .filter(method -> !method.isStatic())
         .map(method -> method.method.toSourceString())
         .sorted()
         .collect(Collectors.toList());
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 53c9f83..2490d41 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.jasmin.JasminBuilder;
@@ -87,22 +88,47 @@
             .build();
 
     // Run the program using java.
-    Path referenceJar = temp.getRoot().toPath().resolve("input.jar");
-    ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar);
-    for (Class<?> clazz : ImmutableList.of(main, A, C)) {
+    String referenceResult;
+    if (backend == Backend.DEX
+        && ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V7_0_0)
+        && ToolHelper.getDexVm().getVersion().isAtLeast(Version.V5_1_1)) {
+      referenceResult =
+          String.join(
+              System.lineSeparator(),
+              "In A.m1()",
+              "In A.m2()",
+              "In A.m3()",
+              "In A.m4()",
+              "In C.m1()",
+              "In A.m2()",
+              "In C.m3()",
+              "In A.m4()",
+              "In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
+              "In A.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
+              "In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
+              "In C.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
+              "In C.m1()",
+              "In C.m3()",
+              "");
+    } else {
+      Path referenceJar = temp.getRoot().toPath().resolve("input.jar");
+      ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar);
+      for (Class<?> clazz : ImmutableList.of(main, A, C)) {
+        inputConsumer.accept(
+            ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
+            DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
+            null);
+      }
       inputConsumer.accept(
-          ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
-          DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
+          ByteDataView.of(jasminBuilder.buildClasses().get(0)),
+          DescriptorUtils.javaTypeToDescriptor(B.getName()),
           null);
-    }
-    inputConsumer.accept(
-        ByteDataView.of(jasminBuilder.buildClasses().get(0)),
-        DescriptorUtils.javaTypeToDescriptor(B.getName()),
-        null);
-    inputConsumer.finished(null);
+      inputConsumer.finished(null);
 
-    ProcessResult referenceResult = ToolHelper.runJava(referenceJar, main.getName());
-    assertEquals(referenceResult.exitCode, 0);
+      ProcessResult javaResult = ToolHelper.runJava(referenceJar, main.getName());
+      assertEquals(javaResult.exitCode, 0);
+      referenceResult = javaResult.stdout;
+    }
 
     // Run the program on Art after is has been compiled with R8.
     AndroidApp compiled =
@@ -115,7 +141,7 @@
               options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
             },
             backend);
-    assertEquals(referenceResult.stdout, runOnVM(compiled, main, backend));
+    assertEquals(referenceResult, runOnVM(compiled, main, backend));
 
     // Check that B is present and that it doesn't contain the unused private method m2.
     if (!enableClassInlining && !enableVerticalClassMerging) {
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 3f66921..f8907d6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -154,17 +154,10 @@
   }
 
   @Before
-  public void resetAllowPartiallyImplementedOptions() {
-    handler = new KeepingDiagnosticHandler();
-    reporter = new Reporter(handler);
-    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
-  }
-
-  @Before
   public void resetAllowTestOptions() {
     handler = new KeepingDiagnosticHandler();
     reporter = new Reporter(handler);
-    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, true, true);
+    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, true);
   }
 
   @Test
@@ -901,7 +894,7 @@
   @Test
   public void parseKeepdirectories() throws Exception {
     ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), reporter, false, false);
+        new ProguardConfigurationParser(new DexItemFactory(), reporter, false);
     parser.parse(Paths.get(KEEPDIRECTORIES));
     verifyParserEndsCleanly();
   }
@@ -1317,7 +1310,6 @@
 
   @Test
   public void parse_adaptresourcexxx_keepdirectories_noArguments1() {
-    resetAllowPartiallyImplementedOptions();
     ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-adaptresourcefilenames",
         "-adaptresourcefilecontents",
@@ -1330,7 +1322,6 @@
 
   @Test
   public void parse_adaptresourcexxx_keepdirectories_noArguments2() {
-    resetAllowPartiallyImplementedOptions();
     ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-keepdirectories",
         "-adaptresourcefilenames",
@@ -1343,7 +1334,6 @@
 
   @Test
   public void parse_adaptresourcexxx_keepdirectories_noArguments3() {
-    resetAllowPartiallyImplementedOptions();
     ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-adaptresourcefilecontents",
         "-keepdirectories",
@@ -1365,7 +1355,6 @@
 
   @Test
   public void parse_adaptresourcexxx_keepdirectories_singleArgument() {
-    resetAllowPartiallyImplementedOptions();
     ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-adaptresourcefilenames " + FILE_FILTER_SINGLE,
         "-adaptresourcefilecontents " + FILE_FILTER_SINGLE,
@@ -1398,7 +1387,6 @@
 
   @Test
   public void parse_adaptresourcexxx_keepdirectories_multipleArgument() {
-    resetAllowPartiallyImplementedOptions();
     ProguardConfiguration config = parseAndVerifyParserEndsCleanly(ImmutableList.of(
         "-adaptresourcefilenames " + FILE_FILTER_MULTIPLE,
         "-adaptresourcefilecontents " + FILE_FILTER_MULTIPLE,
@@ -1415,7 +1403,7 @@
         "-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories");
     for (String option : options) {
       try {
-        resetAllowPartiallyImplementedOptions();
+        reset();
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " ,")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
@@ -1430,7 +1418,7 @@
         "-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories");
     for (String option : options) {
       try {
-        resetAllowPartiallyImplementedOptions();
+        reset();
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,,yyy")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
@@ -1445,7 +1433,7 @@
         "-adaptresourcefilenames", "-adaptresourcefilecontents", "-keepdirectories");
     for (String option : options) {
       try {
-        resetAllowPartiallyImplementedOptions();
+        reset();
         parser.parse(createConfigurationForTesting(ImmutableList.of(option + " xxx,")));
         fail("Expect to fail due to the lack of path filter.");
       } catch (AbortException e) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index af9cfdf..e6de7cc 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -52,6 +52,33 @@
   }
 
   @Test
+  public void ifOnPublic_noPublicClassForIfRule() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-repackageclasses 'top'",
+        "-keep class **.Main* {",
+        "  public static void callIfNonPublic();",
+        "}",
+        "-if public class **.ClassForIf {",
+        "  <methods>;",
+        "}",
+        "-keep,allowobfuscation class **.ClassForSubsequent {",
+        "  public <methods>;",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, not(isPresent()));
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, isPresent());
+    assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+
+    classSubject = codeInspector.clazz(ClassForSubsequent.class);
+    assertThat(classSubject, not(isPresent()));
+  }
+
+  @Test
   public void ifOnNonPublic_keepOnPublic() throws Exception {
     List<String> config = ImmutableList.of(
         "-printmapping",
@@ -191,12 +218,12 @@
     methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, not(isPresent()));
     methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, isPresent());
     if (isR8(shrinker)) {
-      // TODO(b/72109068): if kept in the 1st tree shaking, should be kept after publicizing.
-      assertThat(methodSubject, not(isPresent()));
+      // TODO(b/72109068): if kept in the 1st tree shaking, should not be publicized.
+      assertTrue(methodSubject.getMethod().accessFlags.isPublic());
       return;
     }
-    assertThat(methodSubject, isPresent());
     assertFalse(methodSubject.getMethod().accessFlags.isPublic());
   }
 
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
index ce2992a..00cb62e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -8,7 +8,6 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -84,7 +83,6 @@
         UsedAnnotation.class, UsedAnnotationDependent.class);
   }
 
-  @Ignore("b/116092333")
   @Test
   public void ifOnAnnotation_onDependentClass_withNthWildcard() throws Exception {
     List<String> config = ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index 30a9de9..bdd8e1a 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -18,7 +18,6 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -127,6 +126,20 @@
   }
 
   @Test
+  public void testDependentWithKeepClass() throws Exception {
+    runTest(
+        "-keep @" + pkg + ".JsonClass class ** { <fields>; }",
+        this::checkKeepClassMembers);
+  }
+
+  @Test
+  public void testDependentWithKeepClassAllowObfuscation() throws Exception {
+    runTest(
+        "-keep,allowobfuscation @" + pkg + ".JsonClass class ** { <fields>; }",
+        this::checkKeepClassMembersRenamed);
+  }
+
+  @Test
   public void testDependentWithKeepClassMembers() throws Exception {
     runTest(
         "-keepclassmembers @" + pkg + ".JsonClass class ** { <fields>; }",
@@ -141,7 +154,6 @@
   }
 
   @Test
-  @Ignore("b/116092333")
   public void testDependentWithIfKeepClassMembers() throws Exception {
     runTest(
         "-if @" + pkg + ".JsonClass class * -keepclassmembers class <1> { <fields>; }",
@@ -149,7 +161,6 @@
   }
 
   @Test
-  @Ignore("b/116092333")
   public void testDependentWithIfKeepClassMembersAllowObfuscation() throws Exception {
     runTest(
         "-if @"
diff --git a/third_party/youtube/youtube.android_13.37.tar.gz.sha1 b/third_party/youtube/youtube.android_13.37.tar.gz.sha1
new file mode 100644
index 0000000..ffc92a8
--- /dev/null
+++ b/third_party/youtube/youtube.android_13.37.tar.gz.sha1
@@ -0,0 +1 @@
+533819c8e988bfbcdd7bc3dbd17e0d374c4ba9ab
\ No newline at end of file
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 0cb9019..e040f80 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -4,6 +4,30 @@
 # BSD-style license that can be found in the LICENSE file.
 
 # Run all internal tests, archive result to cloud storage.
+# In the continuous operation flow we have a tester continuously checking
+# a specific cloud storage location for a file with a git hash.
+# If the file is there, the tester will remove the file, and add another
+# file stating that this is now being run. After successfully running,
+# the tester will add yet another file, and remove the last one.
+# Complete flow with states:
+# 1:
+#   BOT:
+#     Add file READY_FOR_TESTING (contains git hash)
+#     Wait until file TESTING_COMPLETE exists (contains git hash)
+#     Timeout if no progress for RUN_TIMEOUT
+#       Cleanup READY_FOR_TESTING and TESTING
+# 2:
+#   TESTER:
+#     Replace file READY_FOR_TESTING by TESTING (contains git hash)
+#     Run tests for git hash
+#     Upload commit specific logs if failures
+#     Upload git specific overall status file (failed or succeeded)
+#     Replace file TESTING by TESTING_COMPLETE (contains git hash)
+# 3:
+#   BOT:
+#     Read overall status
+#     Delete TESTING_COMPLETE
+#     Exit based on status
 
 import optparse
 import os
@@ -12,18 +36,36 @@
 import time
 import utils
 
-# How often to pull the git repo, in seconds.
-PULL_DELAY = 25
+# How often the bot/tester should check state
+PULL_DELAY = 30
 # Command timeout, in seconds.
 RUN_TIMEOUT = 3600
+# Add some extra time for the bot, since the tester might not start immediately.
+BOT_RUN_TIMEOUT = 4000
 BUCKET = 'r8-test-results'
 TEST_RESULT_DIR = 'internal'
 
+# Magic files
+READY_FOR_TESTING = 'READY_FOR_TESTING'
+TESTING = 'TESTING'
+TESTING_COMPLETE = 'TESTING_COMPLETE'
+
+ALL_MAGIC = [READY_FOR_TESTING, TESTING, TESTING_COMPLETE]
+
+# Log file names
+STDERR = 'stderr'
+STDOUT = 'stdout'
+EXITCODE = 'exitcode'
+TIMED_OUT = 'timed_out'
+
 def ParseOptions():
   result = optparse.OptionParser()
   result.add_option('--continuous',
       help='Continuously run internal tests and post results to GCS.',
       default=False, action='store_true')
+  result.add_option('--bot',
+      help='Run in bot mode, i.e., scheduling runs.',
+      default=False, action='store_true')
   result.add_option('--archive',
        help='Post result to GCS, implied by --continuous',
        default=False, action='store_true')
@@ -39,22 +81,40 @@
     print('Restarting tools/internal_test.py, content changed')
     os.execv(sys.argv[0], sys.argv)
 
-def git_pull():
+def ensure_git_clean():
   # Ensure clean git repo.
   diff = subprocess.check_output(['git', 'diff'])
   if len(diff) > 0:
     print('Local modifications to the git repo, exiting')
     sys.exit(1)
+
+def git_pull():
+  ensure_git_clean()
+  subprocess.check_call(['git', 'checkout', 'master'])
   subprocess.check_call(['git', 'pull'])
   return utils.get_HEAD_sha1()
 
+def git_checkout(git_hash):
+  ensure_git_clean()
+  # Ensure that we are up to date to get the commit.
+  git_pull()
+  subprocess.check_call(['git', 'checkout', git_hash])
+  return utils.get_HEAD_sha1()
+
+def get_test_result_dir():
+  return os.path.join(BUCKET, TEST_RESULT_DIR)
+
 def get_sha_destination(sha):
-  return os.path.join(BUCKET, TEST_RESULT_DIR, sha)
+  return os.path.join(get_test_result_dir(), sha)
 
 def archive_status(failed):
   gs_destination = 'gs://%s' % get_sha_destination(utils.get_HEAD_sha1())
   archive_value('status', gs_destination, failed)
 
+def get_status(sha):
+  gs_destination = 'gs://%s/status' % get_sha_destination(sha)
+  return utils.cat_file_on_cloud_storage(gs_destination)
+
 def archive_file(name, gs_dir, src_file):
   gs_file = '%s/%s' % (gs_dir, name)
   utils.upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
@@ -68,30 +128,97 @@
 
 def archive_log(stdout, stderr, exitcode, timed_out, cmd):
   sha = utils.get_HEAD_sha1()
-  cmd_dir = cmd.replace(' ', '_')
+  cmd_dir = cmd.replace(' ', '_').replace('/', '_')
   destination = os.path.join(get_sha_destination(sha), cmd_dir)
   gs_destination = 'gs://%s' % destination
   url = 'https://storage.cloud.google.com/%s' % destination
   print('Archiving logs to: %s' % gs_destination)
-  archive_value('exitcode', gs_destination, exitcode)
-  archive_value('timed_out', gs_destination, timed_out)
-  archive_file('stdout', gs_destination, stdout)
-  archive_file('stderr', gs_destination, stderr)
+  archive_value(EXITCODE, gs_destination, exitcode)
+  archive_value(TIMED_OUT, gs_destination, timed_out)
+  archive_file(STDOUT, gs_destination, stdout)
+  archive_file(STDERR, gs_destination, stderr)
   print('Logs available at: %s' % url)
 
+def get_magic_file_base_path():
+  return 'gs://%s/magic' % get_test_result_dir()
+
+def get_magic_file_gs_path(name):
+  return '%s/%s' % (get_magic_file_base_path(), name)
+
+def get_magic_file_exists(name):
+  return utils.file_exists_on_cloud_storage(get_magic_file_gs_path(name))
+
+def delete_magic_file(name):
+  utils.delete_file_from_cloud_storage(get_magic_file_gs_path(name))
+
+def put_magic_file(name, sha):
+  archive_value(name, get_magic_file_base_path(), sha)
+
+def get_magic_file_content(name, ignore_errors=False):
+  return utils.cat_file_on_cloud_storage(get_magic_file_gs_path(name),
+                                         ignore_errors=ignore_errors)
+
+def print_magic_file_state():
+  print('Magic file status:')
+  for magic in ALL_MAGIC:
+    if get_magic_file_exists(magic):
+      content = get_magic_file_content(magic, ignore_errors=True)
+      print('%s content: %s' % (magic, content))
+
+def run_bot():
+  print_magic_file_state()
+  # Ensure that there is nothing currently scheduled (broken/stopped run)
+  for magic in ALL_MAGIC:
+    if get_magic_file_exists(magic):
+      print('ERROR: Synchronizing file %s exists, cleaning up' % magic)
+      delete_magic_file(magic)
+  print_magic_file_state()
+  assert not get_magic_file_exists(READY_FOR_TESTING)
+  git_hash = utils.get_HEAD_sha1()
+  put_magic_file(READY_FOR_TESTING, git_hash)
+  begin = time.time()
+  while True:
+    if time.time() - begin > BOT_RUN_TIMEOUT:
+      print('Timeout exceeded')
+      raise Exception('Bot timeout')
+    if get_magic_file_exists(TESTING_COMPLETE):
+      if get_magic_file_content(TESTING_COMPLETE) == git_hash:
+        break
+      else:
+        raise Exception('Non matching git hashes %s and %s' % (
+            get_magic_file_content(TESTING_COMPLETE), git_hash))
+    print('Still waiting for test result')
+    print_magic_file_state()
+    time.sleep(PULL_DELAY)
+  total_time = time.time()-begin
+  print('Done running test for %s in %ss' % (git_hash, total_time))
+  test_status = get_status(git_hash)
+  delete_magic_file(TESTING_COMPLETE)
+  print('Test status is: %s' % test_status)
+  if test_status != '0':
+    return 1
+
 def run_continuously():
   # If this script changes, we will restart ourselves
   own_content = get_own_file_content()
-  git_hash = utils.get_HEAD_sha1()
   while True:
     restart_if_new_version(own_content)
-    print('Running with hash: %s' % git_hash)
-    exitcode = run_once(archive=True)
-    git_pull()
-    while git_pull() == git_hash:
-      print('Still on same git hash: %s' % git_hash)
-      time.sleep(PULL_DELAY)
-    git_hash = utils.get_HEAD_sha1()
+    print_magic_file_state()
+    if get_magic_file_exists(READY_FOR_TESTING):
+      git_hash = get_magic_file_content(READY_FOR_TESTING)
+      checked_out = git_checkout(git_hash)
+      # Sanity check, if this does not succeed stop.
+      if checked_out != git_hash:
+        print('Inconsistent state: %s %s' % (git_hash, checked_out))
+        sys.exit(1)
+      put_magic_file(TESTING, git_hash)
+      delete_magic_file(READY_FOR_TESTING)
+      print('Running with hash: %s' % git_hash)
+      exitcode = run_once(archive=True)
+      print('Running finished with exit code %s' % exitcode)
+      put_magic_file(TESTING_COMPLETE, git_hash)
+      delete_magic_file(TESTING)
+    time.sleep(PULL_DELAY)
 
 def handle_output(archive, stderr, stdout, exitcode, timed_out, cmd):
   if archive:
@@ -147,17 +274,21 @@
   if execute(cmd, archive):
     failed = True
   # Ensure that all internal apps compile.
-  cmd = ['tools/run_on_app.py', '--run-all', '--out=out']
+  cmd = ['tools/run_on_app.py', '--ignore-java-version','--run-all',
+         '--out=out']
   if execute(cmd, archive):
     failed = True
   archive_status(1 if failed else 0)
+  return failed
 
 def Main():
   (options, args) = ParseOptions()
   if options.continuous:
     run_continuously()
+  elif options.bot:
+    return run_bot()
   else:
-    run_once(options.archive)
+    return run_once(options.archive)
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 0667abe..bab9b32 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -49,6 +49,10 @@
                     help='Running on golem, do not build or download',
                     default=False,
                     action='store_true')
+  result.add_option('--ignore-java-version',
+                    help='Do not check java version',
+                    default=False,
+                    action='store_true')
   result.add_option('--no-libraries',
                     help='Do not pass in libraries, even if they exist in conf',
                     default=False,
@@ -115,7 +119,8 @@
 # do Bug: #BUG in the commit message of disabling to ensure re-enabling
 DISABLED_PERMUTATIONS = [
     ('youtube', '12.10', 'dex'), # b/116089492
-    ('youtube', '12.22', 'deploy') # b/116093710
+    ('youtube', '12.10', 'proguarded'), # b/116089492
+    ('gmscore', 'latest', 'deploy') # b/116575775
 ]
 
 def get_permutations():
@@ -150,8 +155,10 @@
       exit(exit_code)
 
 def main(argv):
-  utils.check_java_version()
   (options, args) = ParseOptions(argv)
+  if not options.ignore_java_version:
+    utils.check_java_version()
+
   if options.run_all:
     return run_all(options, args)
   return run_with_options(options, args)
diff --git a/tools/utils.py b/tools/utils.py
index d96151d..20ba093 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -106,6 +106,27 @@
   PrintCmd(cmd)
   subprocess.check_call(cmd)
 
+def delete_file_from_cloud_storage(destination):
+  cmd = ['gsutil.py', 'rm', destination]
+  PrintCmd(cmd)
+  subprocess.check_call(cmd)
+
+def cat_file_on_cloud_storage(destination, ignore_errors=False):
+  cmd = ['gsutil.py', 'cat', destination]
+  PrintCmd(cmd)
+  try:
+    return subprocess.check_output(cmd)
+  except subprocess.CalledProcessError as e:
+    if ignore_errors:
+      return ''
+    else:
+      raise e
+
+def file_exists_on_cloud_storage(destination):
+  cmd = ['gsutil.py', 'ls', destination]
+  PrintCmd(cmd)
+  return subprocess.call(cmd) == 0
+
 def download_file_from_cloud_storage(source, destination):
   cmd = ['gsutil.py', 'cp', source, destination]
   PrintCmd(cmd)
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index b7f1ee7..6fdf63a 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -19,6 +19,9 @@
 V12_22_BASE = os.path.join(BASE, 'youtube.android_12.22')
 V12_22_PREFIX = os.path.join(V12_22_BASE, 'YouTubeRelease')
 
+V13_37_BASE = os.path.join(BASE, 'youtube.android_13.37')
+V13_37_PREFIX = os.path.join(V13_37_BASE, 'YouTubeRelease')
+
 # NOTE: we always use android.jar for SDK v25, later we might want to revise it
 #       to use proper android.jar version for each of youtube version separately.
 ANDROID_JAR = os.path.join(THIRD_PARTY, 'android_jar', 'lib-v25', 'android.jar')
@@ -85,4 +88,30 @@
       'min-api' : ANDROID_L_API,
     }
   },
+  '13.37': {
+    'dex' : {
+      'inputs': [os.path.join(V13_37_BASE, 'YouTubeRelease_unsigned.apk')],
+      'pgmap': '%s_proguard.map' % V13_37_PREFIX,
+      'libraries' : [ANDROID_JAR],
+      'min-api' : ANDROID_L_API,
+    },
+    'deploy' : {
+      'inputs': ['%s_deploy.jar' % V13_37_PREFIX],
+      'pgconf': [
+          '%s_proguard.config' % V13_37_PREFIX,
+          '%s/proguardsettings/YouTubeRelease_proguard.config' % THIRD_PARTY],
+      # Build for native multi dex, as Currently R8 cannot meet the main-dex
+      # constraints.
+      #'maindexrules' : [
+      #    os.path.join(V13_37_BASE, 'mainDexClasses.rules'),
+      #    os.path.join(V13_37_BASE, 'main-dex-classes-release-optimized.cfg'),
+      #    os.path.join(V13_37_BASE, 'main_dex_YouTubeRelease_proguard.cfg')],
+      'min-api' : ANDROID_L_API,
+    },
+    'proguarded' : {
+      'inputs': ['%s_proguard.jar' % V13_37_PREFIX],
+      'pgmap': '%s_proguard.map' % V13_37_PREFIX,
+      'min-api' : ANDROID_L_API,
+    }
+  },
 }