Merge "Add YouTube version 13.37"
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/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index 6ccacf8..08af875 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -57,9 +57,9 @@
       join = TypeLatticeElement.join(appInfo, join, getLatticeElement(iterator.next()));
     }
     // All types are reference types so the join is either a class or an array.
-    if (join.isClassTypeLatticeElement()) {
+    if (join.isClassType()) {
       return join.asClassTypeLatticeElement().getClassType();
-    } else if (join.isArrayTypeLatticeElement()) {
+    } else if (join.isArrayType()) {
       return join.asArrayTypeLatticeElement().getArrayType();
     }
     throw new CompilationError("Unexpected join " + join + " of types: " +
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 1ed60c0..150e516 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -138,7 +138,12 @@
       DexClass clazz = definitionFor(type);
       if (!clazz.isInterface()) {
         ResolutionResult methods = resolveMethodOnClass(type, method);
-        methods.forEachTarget(result::add);
+        methods.forEachTarget(
+            target -> {
+              if (target.isVirtualMethod()) {
+                result.add(target);
+              }
+            });
       }
     }
     return result;
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..a4986a5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -216,11 +216,24 @@
   /**
    * Returns true if this method can be invoked via invoke-static.
    */
+  // TODO(jsjeon): deprecate this, and use isStatic() instead.
   public boolean isStaticMethod() {
     checkIfObsolete();
     return accessFlags.isStatic();
   }
 
+  @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/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index 1568531..c8f2b8c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -7,42 +7,41 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 
-public class ArrayTypeLatticeElement extends TypeLatticeElement {
-  private final DexType arrayType;
+public class ArrayTypeLatticeElement extends ReferenceTypeLatticeElement {
 
-  ArrayTypeLatticeElement(DexType arrayType, boolean isNullable) {
-    super(isNullable);
-    this.arrayType = arrayType;
+  ArrayTypeLatticeElement(DexType type, boolean isNullable) {
+    super(type, isNullable);
+    assert type.isArrayType();
   }
 
   public DexType getArrayType() {
-    return arrayType;
+    return type;
   }
 
   public int getNesting() {
-    return arrayType.getNumberOfLeadingSquareBrackets();
+    return type.getNumberOfLeadingSquareBrackets();
   }
 
   public DexType getArrayElementType(DexItemFactory factory) {
-    return arrayType.toArrayElementType(factory);
+    return type.toArrayElementType(factory);
   }
 
   public DexType getArrayBaseType(DexItemFactory factory) {
-    return arrayType.toBaseType(factory);
+    return type.toBaseType(factory);
   }
 
   @Override
   TypeLatticeElement asNullable() {
-    return isNullable() ? this : new ArrayTypeLatticeElement(arrayType, true);
+    return isNullable() ? this : new ArrayTypeLatticeElement(type, true);
   }
 
   @Override
   public TypeLatticeElement asNonNullable() {
-    return isNullable() ? new ArrayTypeLatticeElement(arrayType, false) : this;
+    return isNullable() ? new ArrayTypeLatticeElement(type, false) : this;
   }
 
   @Override
-  public boolean isArrayTypeLatticeElement() {
+  public boolean isArrayType() {
     return true;
   }
 
@@ -58,20 +57,7 @@
 
   @Override
   public String toString() {
-    return isNullableString() + arrayType.toString();
+    return isNullableString() + type.toString();
   }
 
-  @Override
-  public boolean equals(Object o) {
-    if (!super.equals(o)) {
-      return false;
-    }
-    ArrayTypeLatticeElement other = (ArrayTypeLatticeElement) o;
-    return arrayType.equals(other.arrayType);
-  }
-
-  @Override
-  public int hashCode() {
-    return super.hashCode() * arrayType.hashCode() * 41;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 9144f52..3efc915 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -5,28 +5,22 @@
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
-import com.google.common.collect.ImmutableSet;
-import java.util.Collections;
 import java.util.Set;
-import java.util.stream.Collectors;
 
-public class ClassTypeLatticeElement extends TypeLatticeElement {
-  private final DexType classType;
-  private final Set<DexType> interfaces;
+public class ClassTypeLatticeElement extends ReferenceTypeLatticeElement {
 
   ClassTypeLatticeElement(DexType classType, boolean isNullable) {
-    this(classType, isNullable, ImmutableSet.of());
+    super(classType, isNullable);
+    assert classType.isClassType();
   }
 
   ClassTypeLatticeElement(DexType classType, boolean isNullable, Set<DexType> interfaces) {
-    super(isNullable);
+    super(classType, isNullable, interfaces);
     assert classType.isClassType();
-    this.classType = classType;
-    this.interfaces = Collections.unmodifiableSet(interfaces);
   }
 
   public DexType getClassType() {
-    return classType;
+    return type;
   }
 
   Set<DexType> getInterfaces() {
@@ -35,16 +29,16 @@
 
   @Override
   TypeLatticeElement asNullable() {
-    return isNullable() ? this : new ClassTypeLatticeElement(classType, true, interfaces);
+    return isNullable() ? this : new ClassTypeLatticeElement(type, true, interfaces);
   }
 
   @Override
   public TypeLatticeElement asNonNullable() {
-    return isNullable() ? new ClassTypeLatticeElement(classType, false, interfaces) : this;
+    return isNullable() ? new ClassTypeLatticeElement(type, false, interfaces) : this;
   }
 
   @Override
-  public boolean isClassTypeLatticeElement() {
+  public boolean isClassType() {
     return true;
   }
 
@@ -58,37 +52,4 @@
     return objectType(appInfo, true);
   }
 
-  @Override
-  public String toString() {
-    StringBuilder builder = new StringBuilder();
-    builder.append(isNullableString()).append(classType.toString());
-    if (!interfaces.isEmpty()) {
-      builder.append(" [");
-      builder.append(
-          interfaces.stream().map(DexType::toString).collect(Collectors.joining(", ")));
-      builder.append("]");
-    }
-    return builder.toString();
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!super.equals(o)) {
-      return false;
-    }
-    ClassTypeLatticeElement other = (ClassTypeLatticeElement) o;
-    if (!classType.equals(other.classType)) {
-      return false;
-    }
-    if (interfaces.size() != other.interfaces.size()) {
-      return false;
-    }
-    return interfaces.containsAll(other.interfaces);
-  }
-
-  @Override
-  public int hashCode() {
-    int prime = (!classType.isUnknown() && classType.isInterface()) ? 3 : 17;
-    return super.hashCode() * classType.hashCode() * prime;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
deleted file mode 100644
index efb340f..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.analysis.type;
-
-import com.android.tools.r8.graph.AppInfo;
-
-public class NullLatticeElement extends TypeLatticeElement {
-  private static final NullLatticeElement INSTANCE = new NullLatticeElement();
-
-  private NullLatticeElement() {
-    super(true);
-  }
-
-  @Override
-  public boolean mustBeNull() {
-    return true;
-  }
-
-  @Override
-  TypeLatticeElement asNullable() {
-    return this;
-  }
-
-  @Override
-  public TypeLatticeElement asNonNullable() {
-    return BottomTypeLatticeElement.getInstance();
-  }
-
-  public static NullLatticeElement getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public TypeLatticeElement arrayGet(AppInfo appInfo) {
-    return this;
-  }
-
-  @Override
-  public String toString() {
-    return "NULL";
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
new file mode 100644
index 0000000..f700b15
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -0,0 +1,90 @@
+// 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.ir.analysis.type;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class ReferenceTypeLatticeElement extends TypeLatticeElement {
+  private static final ReferenceTypeLatticeElement NULL =
+      new ReferenceTypeLatticeElement(DexItemFactory.nullValueType, true);
+
+  final DexType type;
+  final Set<DexType> interfaces;
+
+  ReferenceTypeLatticeElement(DexType type, boolean isNullable) {
+    this(type, isNullable, ImmutableSet.of());
+  }
+
+  ReferenceTypeLatticeElement(DexType type, boolean isNullable, Set<DexType> interfaces) {
+    super(isNullable);
+    this.type = type;
+    this.interfaces = Collections.unmodifiableSet(interfaces);
+  }
+
+  public static ReferenceTypeLatticeElement getNullTypeLatticeElement() {
+    return NULL;
+  }
+
+  @Override
+  public boolean isNull() {
+    return type == DexItemFactory.nullValueType;
+  }
+
+  @Override
+  TypeLatticeElement asNullable() {
+    assert isNull();
+    return this;
+  }
+
+  @Override
+  public boolean isReference() {
+    return true;
+  }
+
+  @Override
+  public TypeLatticeElement arrayGet(AppInfo appInfo) {
+    assert isNull();
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append(isNullableString()).append(type.toString());
+    if (!interfaces.isEmpty()) {
+      builder.append(" [");
+      builder.append(
+          interfaces.stream().map(DexType::toString).collect(Collectors.joining(", ")));
+      builder.append("]");
+    }
+    return builder.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!super.equals(o)) {
+      return false;
+    }
+    ReferenceTypeLatticeElement other = (ReferenceTypeLatticeElement) o;
+    if (!type.equals(other.type)) {
+      return false;
+    }
+    if (interfaces.size() != other.interfaces.size()) {
+      return false;
+    }
+    return interfaces.containsAll(other.interfaces);
+  }
+
+  @Override
+  public int hashCode() {
+    int prime = type.isUnknown() ? 3 : (type.isArrayType() ? 7 : 5);
+    return super.hashCode() * type.hashCode() * prime;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 90a864c..ab77424 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -150,7 +150,7 @@
       AppInfoWithSubtyping appInfo, InvokeMethodWithReceiver invoke) {
     DexType receiverType = invoke.getInvokedMethod().getHolder();
     TypeLatticeElement lattice = invoke.getReceiver().getTypeLattice();
-    if (lattice.isClassTypeLatticeElement()) {
+    if (lattice.isClassType()) {
       DexType refinedType = lattice.asClassTypeLatticeElement().getClassType();
       if (refinedType.isSubtypeOf(receiverType, appInfo)) {
         return refinedType;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index f9d327f..5fffc8d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -33,7 +33,7 @@
     return isNullable;
   }
 
-  public boolean mustBeNull() {
+  public boolean isNull() {
     return false;
   }
 
@@ -50,7 +50,7 @@
    * @return {@link TypeLatticeElement} a similar lattice element with nullable flag flipped.
    */
   public TypeLatticeElement asNonNullable() {
-    throw new Unreachable("Flipping nullable is not allowed in general.");
+    return BottomTypeLatticeElement.getInstance();
   }
 
   String isNullableString() {
@@ -76,10 +76,10 @@
     if (l1.isTop() || l2.isTop()) {
       return TopTypeLatticeElement.getInstance();
     }
-    if (l1.mustBeNull()) {
+    if (l1.isNull()) {
       return l2.asNullable();
     }
-    if (l2.mustBeNull()) {
+    if (l2.isNull()) {
       return l1.asNullable();
     }
     if (l1.isPrimitive()) {
@@ -98,8 +98,8 @@
       return objectType(appInfo, isNullable);
     }
     // From now on, l1.getClass() == l2.getClass()
-    if (l1.isArrayTypeLatticeElement()) {
-      assert l2.isArrayTypeLatticeElement();
+    if (l1.isArrayType()) {
+      assert l2.isArrayType();
       ArrayTypeLatticeElement a1 = l1.asArrayTypeLatticeElement();
       ArrayTypeLatticeElement a2 = l2.asArrayTypeLatticeElement();
       // Identical types are the same elements
@@ -136,8 +136,8 @@
       DexType arrayTypeLub = appInfo.dexItemFactory.createArrayType(a1Nesting, lub);
       return new ArrayTypeLatticeElement(arrayTypeLub, isNullable);
     }
-    if (l1.isClassTypeLatticeElement()) {
-      assert l2.isClassTypeLatticeElement();
+    if (l1.isClassType()) {
+      assert l2.isClassType();
       ClassTypeLatticeElement c1 = l1.asClassTypeLatticeElement();
       ClassTypeLatticeElement c2 = l2.asClassTypeLatticeElement();
       DexType lubType =
@@ -289,7 +289,11 @@
     return false;
   }
 
-  public boolean isArrayTypeLatticeElement() {
+  public boolean isReference() {
+    return false;
+  }
+
+  public boolean isArrayType() {
     return false;
   }
 
@@ -297,7 +301,7 @@
     return null;
   }
 
-  public boolean isClassTypeLatticeElement() {
+  public boolean isClassType() {
     return false;
   }
 
@@ -338,8 +342,8 @@
   }
 
   public boolean isPreciseType() {
-    return isArrayTypeLatticeElement()
-        || isClassTypeLatticeElement()
+    return isArrayType()
+        || isClassType()
         || isInt()
         || isFloat()
         || isLong()
@@ -358,7 +362,7 @@
 
   public static TypeLatticeElement fromDexType(AppInfo appInfo, DexType type, boolean isNullable) {
     if (type == DexItemFactory.nullValueType) {
-      return NullLatticeElement.getInstance();
+      return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
     }
     if (type.isPrimitiveType()) {
       return PrimitiveTypeLatticeElement.fromDexType(type);
@@ -386,7 +390,7 @@
   public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
     TypeLatticeElement castTypeLattice = fromDexType(appInfo, castType, isNullable());
     // Special case: casting null.
-    if (mustBeNull()) {
+    if (isNull()) {
       return castTypeLattice;
     }
     if (lessThanOrEqual(appInfo, this, castTypeLattice)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 21a3293..6f5fa47 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -26,7 +26,7 @@
 import com.android.tools.r8.ir.analysis.type.FloatTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.IntTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.LongTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.NullLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.SingleTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.WideTypeLatticeElement;
@@ -276,13 +276,10 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    if (isZero() && outType().isObject()) {
-      return NullLatticeElement.getInstance();
-    }
     // TODO(b/72693244): IR builder should know the type and assign a proper type lattice.
     switch (outType()) {
       case OBJECT:
-        return NullLatticeElement.getInstance();
+        return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
       case INT:
         return IntTypeLatticeElement.getInstance();
       case FLOAT:
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/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 539d943..b2942d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -91,19 +91,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    TypeLatticeElement l = src().getTypeLatticeRaw();
-    // Flipping the nullability bit for reference type is the main use case.
-    if (l.isClassTypeLatticeElement() || l.isArrayTypeLatticeElement()) {
-      return l.asNonNullable();
-    }
-    // non_null_rcv <- non-null NULL ?!
-    // The chances are that the in is phi, and the only available operand is null. If another
-    // operand is, say, class A, phi's type is nullable A, and the out value of this instruction
-    // would be non-null A. Until that phi is saturated, we will ignore the current null.
-    if (l.mustBeNull()) {
-      return l.asNonNullable();
-    }
-    return l;
+    return src().getTypeLatticeRaw().asNonNullable();
   }
 
   @Override
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 cad7504..194558e 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
@@ -1334,7 +1334,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();
@@ -1344,7 +1344,7 @@
       } else if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
         if (staticGet.getField().name == dexItemFactory.assertionsDisabled) {
-          iterator.replaceCurrentInstruction(code.createTrue());
+          iterator.replaceCurrentInstruction(code.createIntConstant(1));
         }
       }
     }
@@ -1644,7 +1644,7 @@
             TypeLatticeElement.fromDexType(appInfo, castType, inTypeLattice.isNullable());
         // Special case: null cast, e.g., getMethod(..., (Class[]) null);
         // This cast should be kept no matter what.
-        if (inTypeLattice.mustBeNull()) {
+        if (inTypeLattice.isNull()) {
           assert outTypeLattice.equals(castTypeLattice);
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 3bb6efd..9baa647 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
@@ -248,7 +249,8 @@
 
       // Eligible virtual method call on the instance as a receiver.
       if (user.isInvokeVirtual() || user.isInvokeInterface()) {
-        InliningInfo inliningInfo = isEligibleDirectMethodCall(user.asInvokeMethodWithReceiver());
+        InliningInfo inliningInfo =
+            isEligibleDirectVirtualMethodCall(user.asInvokeMethodWithReceiver());
         if (inliningInfo != null) {
           methodCallsOnInstance.put(user.asInvokeMethodWithReceiver(), inliningInfo);
           continue;
@@ -504,24 +506,39 @@
         ? new InliningInfo(definition, eligibleClass) : null;
   }
 
-  private InliningInfo isEligibleDirectMethodCall(InvokeMethodWithReceiver invoke) {
+  private InliningInfo isEligibleDirectVirtualMethodCall(InvokeMethodWithReceiver invoke) {
     if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
       return null; // Instance passed as an argument.
     }
-    return isEligibleMethodCall(!invoke.getBlock().hasCatchHandlers(), invoke.getInvokedMethod(),
-        eligibility -> !eligibility.returnsReceiver ||
-            invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0);
+    return isEligibleVirtualMethodCall(
+        !invoke.getBlock().hasCatchHandlers(),
+        invoke.getInvokedMethod(),
+        eligibility ->
+            !eligibility.returnsReceiver
+                || invoke.outValue() == null
+                || invoke.outValue().numberOfAllUsers() == 0);
   }
 
-  private InliningInfo isEligibleIndirectMethodCall(DexMethod callee) {
-    return isEligibleMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver);
+  private InliningInfo isEligibleIndirectVirtualMethodCall(DexMethod callee) {
+    return isEligibleVirtualMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver);
   }
 
-  private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
-      DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
+  private InliningInfo isEligibleVirtualMethodCall(
+      boolean allowMethodsWithoutNormalReturns,
+      DexMethod callee,
+      Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
+    // We should not inline a method if the invocation has type interface or virtual and the
+    // signature of the invocation resolves to a private or static method.
+    ResolutionResult resolutionResult = appInfo.resolveMethod(callee.holder, callee);
+    if (resolutionResult.hasSingleTarget()
+        && !resolutionResult.asSingleTarget().isVirtualMethod()) {
+      return null;
+    }
 
     DexEncodedMethod singleTarget = findSingleTarget(callee, false);
-    if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
+    if (singleTarget == null
+        || !singleTarget.isVirtualMethod()
+        || isProcessedConcurrently.test(singleTarget)) {
       return null;
     }
     if (method == singleTarget) {
@@ -662,7 +679,7 @@
         }
 
         // Is the method called indirectly still eligible?
-        InliningInfo potentialInliningInfo = isEligibleIndirectMethodCall(call.getSecond());
+        InliningInfo potentialInliningInfo = isEligibleIndirectVirtualMethodCall(call.getSecond());
         if (potentialInliningInfo == null) {
           return false;
         }
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 41551e7..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);
     }
@@ -1136,10 +1136,10 @@
               while (!worklist.isEmpty()) {
                 DexType current = worklist.pollFirst();
                 DexClass currentHolder = appInfo.definitionFor(current);
-                // If this class shadows the virtual, abort the search. Note, according to JVM spec,
-                // shadowing is independent of whether a method is public or private.
+                // If this class overrides the virtual, abort the search. Note that, according to
+                // the JVM spec, private methods cannot override a virtual method.
                 if (currentHolder == null
-                    || currentHolder.lookupMethod(encodedMethod.method) != null) {
+                    || currentHolder.lookupVirtualMethod(encodedMethod.method) != null) {
                   continue;
                 }
                 if (instantiatedTypes.contains(current)) {
@@ -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;
           }
@@ -2191,8 +2201,8 @@
       }
       for (DexType subtype : type.allExtendsSubtypes()) {
         DexClass clazz = definitionFor(subtype);
-        DexEncodedMethod target = clazz.lookupMethod(method);
-        if (target != null) {
+        DexEncodedMethod target = clazz.lookupVirtualMethod(method);
+        if (target != null && !target.isPrivateMethod()) {
           // We found a method on this class. If this class is not abstract it is a runtime
           // reachable override and hence a conflict.
           if (!clazz.accessFlags.isAbstract()) {
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 783e133..43f8d20 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -678,6 +678,17 @@
   }
 
   private boolean methodResolutionMayChange(DexClass source, DexClass target) {
+    for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
+      DexEncodedMethod directTargetMethod = target.lookupDirectMethod(virtualSourceMethod.method);
+      if (directTargetMethod != null) {
+        // A private method shadows a virtual method. This situation is rare, since it is not
+        // allowed by javac. Therefore, we just give up in this case. (In principle, it would be
+        // possible to rename the private method in the subclass, and then move the virtual method
+        // to the subclass without changing its name.)
+        return true;
+      }
+    }
+
     // When merging an interface into a class, all instructions on the form "invoke-interface
     // [source].m" are changed into "invoke-virtual [target].m". We need to abort the merge if this
     // transformation could hide IncompatibleClassChangeErrors.
@@ -936,9 +947,10 @@
         assert !target.isInterface();
         target.superType = source.superType;
       }
-      target.interfaces = interfaces.isEmpty()
-          ? DexTypeList.empty()
-          : new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
+      target.interfaces =
+          interfaces.isEmpty()
+              ? DexTypeList.empty()
+              : new DexTypeList(interfaces.toArray(new DexType[0]));
       // Step 2: replace fields and methods.
       target.setDirectMethods(mergedDirectMethods);
       target.setVirtualMethods(mergedVirtualMethods);
@@ -1095,6 +1107,7 @@
       }
       DexEncodedMethod actual = resolutionResult.asSingleTarget();
       if (actual != method) {
+        assert actual.isVirtualMethod() == method.isVirtualMethod();
         return actual;
       }
       // We will keep the method, so the class better be abstract if there is no implementation.
@@ -1325,7 +1338,7 @@
       if (filtered.size() == methods.length) {
         return methods;
       }
-      return filtered.values().toArray(new DexEncodedMethod[filtered.size()]);
+      return filtered.values().toArray(DexEncodedMethod.EMPTY_ARRAY);
     }
 
     private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 11bdfd3..0ea8731 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -68,7 +68,7 @@
     if (l.isPrimitive()) {
       return;
     }
-    assertTrue(l.isClassTypeLatticeElement());
+    assertTrue(l.isClassType());
     ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
     // Receiver
     if (lattice.getClassType().equals(receiverType)) {
@@ -156,13 +156,13 @@
           ArrayGet.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
           NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
       forEachOutValue(irCode, (v, l) -> {
-        if (l.isArrayTypeLatticeElement()) {
+        if (l.isArrayType()) {
           ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
           assertEquals(
               appInfo.dexItemFactory.stringType,
               lattice.getArrayElementType(appInfo.dexItemFactory));
           assertEquals(v.definition.isArgument(), l.isNullable());
-        } else if (l.isClassTypeLatticeElement()) {
+        } else if (l.isClassType()) {
           verifyClassTypeLattice(expectedLattices, mainClass, v, l);
         }
       });
@@ -182,13 +182,13 @@
           ArrayGet.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
           NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
       forEachOutValue(irCode, (v, l) -> {
-        if (l.isArrayTypeLatticeElement()) {
+        if (l.isArrayType()) {
           ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
           assertEquals(
               appInfo.dexItemFactory.stringType,
               lattice.getArrayElementType(appInfo.dexItemFactory));
           assertEquals(v.definition.isArgument(), l.isNullable());
-        } else if (l.isClassTypeLatticeElement()) {
+        } else if (l.isClassType()) {
           verifyClassTypeLattice(expectedLattices, mainClass, v, l);
         }
       });
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index a2f8d3e..75cef4d 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -58,7 +58,8 @@
 @RunWith(Parameterized.class)
 public class TypeAnalysisTest extends SmaliTestBase {
   private static final InternalOptions TEST_OPTIONS = new InternalOptions();
-  private static final TypeLatticeElement NULL = NullLatticeElement.getInstance();
+  private static final TypeLatticeElement NULL =
+      ReferenceTypeLatticeElement.getNullTypeLatticeElement();
   private static final TypeLatticeElement SINGLE = SingleTypeLatticeElement.getInstance();
   private static final TypeLatticeElement INT = IntTypeLatticeElement.getInstance();
   private static final TypeLatticeElement LONG = LongTypeLatticeElement.getInstance();
@@ -187,7 +188,7 @@
     final Value finalArray = array;
     forEachOutValue(irCode, (v, l) -> {
       if (v == finalArray) {
-        assertTrue(l.isArrayTypeLatticeElement());
+        assertTrue(l.isArrayType());
         ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
         assertTrue(lattice.getArrayType().isPrimitiveArrayType());
         assertEquals(1, lattice.getNesting());
@@ -220,7 +221,7 @@
     final Value finalArray = array;
     forEachOutValue(irCode, (v, l) -> {
       if (v == finalArray) {
-        assertTrue(l.isArrayTypeLatticeElement());
+        assertTrue(l.isArrayType());
         ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
         assertTrue(lattice.getArrayType().isPrimitiveArrayType());
         assertEquals(1, lattice.getNesting());
@@ -241,7 +242,7 @@
     TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2);
     analysis.widening(loop2, irCode);
     forEachOutValue(irCode, (v, l) -> {
-      if (l.isClassTypeLatticeElement()) {
+      if (l.isClassType()) {
         ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
         assertEquals("Ljava/io/PrintStream;", lattice.getClassType().toDescriptorString());
         // TODO(b/70795205): Can be refined by using control-flow info.
@@ -262,7 +263,7 @@
     TypeAnalysis analysis = new TypeAnalysis(appInfo, test2);
     analysis.widening(test2, irCode);
     forEachOutValue(irCode, (v, l) -> {
-      if (l.isClassTypeLatticeElement()) {
+      if (l.isClassType()) {
         ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
         assertEquals("Ljava/lang/Throwable;", lattice.getClassType().toDescriptorString());
         assertFalse(l.isNullable());
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 15d5221..eb2c54b 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -405,7 +405,7 @@
         array(2, factory.objectType),
         array(1, factory.objectType)));
     assertTrue(strictlyLessThan(
-        NullLatticeElement.getInstance(),
+        ReferenceTypeLatticeElement.getNullTypeLatticeElement(),
         array(1, factory.classType)));
   }
 
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 23c8933..fbf4e68 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo;
 import com.android.tools.r8.resolution.singletarget.three.ThirdAbstractTopClass;
 import com.android.tools.r8.resolution.singletarget.three.ThirdSubClassOne;
-import com.android.tools.r8.resolution.singletarget.three.ThirdSubClassTwo;
 import com.android.tools.r8.resolution.singletarget.three.ThirdSubClassTwoDump;
 import com.android.tools.r8.resolution.singletarget.two.OtherAbstractSubClassOne;
 import com.android.tools.r8.resolution.singletarget.two.OtherAbstractSubClassTwo;
@@ -191,10 +190,9 @@
             IrrelevantInterfaceWithDefault.class),
         manyTargets("abstractMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class,
             ThirdSubClassOne.class),
-        manyTargets("instanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class,
-            ThirdSubClassTwo.class),
-        manyTargets("otherInstanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class,
-            ThirdSubClassTwo.class),
+        singleTarget("instanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class),
+        singleTarget(
+            "otherInstanceMethod", ThirdAbstractTopClass.class, ThirdAbstractTopClass.class)
     });
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
new file mode 100644
index 0000000..2490d41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -0,0 +1,267 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ByteDataView;
+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;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NonVirtualOverrideTest extends TestBase {
+
+  private static Class<?> main = NonVirtualOverrideTestClass.class;
+  private static Class<?> A = NonVirtualOverrideTestClass.A.class;
+  private static Class<?> B = NonVirtualOverrideTestClass.B.class;
+  private static Class<?> C = NonVirtualOverrideTestClass.C.class;
+
+  private final Backend backend;
+  private final boolean enableClassInlining;
+  private final boolean enableVerticalClassMerging;
+
+  @Parameterized.Parameters(name = "Backend: {0}, class inlining: {1}, vertical class merging: {2}")
+  public static Collection<Object[]> data() {
+    ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
+    for (Backend backend : Backend.values()) {
+      for (boolean enableClassInlining : ImmutableList.of(true, false)) {
+        for (boolean enableVerticalClassMerging : ImmutableList.of(true, false)) {
+          builder.add(new Object[] {backend, enableClassInlining, enableVerticalClassMerging});
+        }
+      }
+    }
+    return builder.build();
+  }
+
+  public NonVirtualOverrideTest(
+      Backend backend, boolean enableClassInlining, boolean enableVerticalClassMerging) {
+    this.backend = backend;
+    this.enableClassInlining = enableClassInlining;
+    this.enableVerticalClassMerging = enableVerticalClassMerging;
+  }
+
+  @Test
+  public void test() throws Exception {
+    // Construct B such that it inherits from A and shadows method A.m() with a private method.
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    ClassBuilder classBuilder = jasminBuilder.addClass(B.getName(), A.getName());
+    classBuilder.addDefaultConstructor();
+    for (String methodName : ImmutableList.of("m1", "m2")) {
+      classBuilder.addPrivateVirtualMethod(
+          methodName, ImmutableList.of(), "V", jasminCodeForPrinting("In B." + methodName + "()"));
+    }
+    for (String methodName : ImmutableList.of("m3", "m4")) {
+      classBuilder.addStaticMethod(
+          methodName, ImmutableList.of(), "V", jasminCodeForPrinting("In B." + methodName + "()"));
+    }
+
+    AndroidApp input =
+        AndroidApp.builder()
+            .addProgramFiles(
+                ToolHelper.getClassFileForTestClass(main),
+                ToolHelper.getClassFileForTestClass(A),
+                ToolHelper.getClassFileForTestClass(C))
+            .addClassProgramData(jasminBuilder.buildClasses())
+            .build();
+
+    // Run the program using java.
+    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(jasminBuilder.buildClasses().get(0)),
+          DescriptorUtils.javaTypeToDescriptor(B.getName()),
+          null);
+      inputConsumer.finished(null);
+
+      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 =
+        compileWithR8(
+            input,
+            keepMainProguardConfiguration(main),
+            options -> {
+              options.enableClassInlining = enableClassInlining;
+              options.enableVerticalClassMerging = enableVerticalClassMerging;
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+            },
+            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) {
+      CodeInspector inspector = new CodeInspector(compiled);
+      ClassSubject classSubject = inspector.clazz(B.getName());
+      assertThat(classSubject, isRenamed());
+      assertThat(classSubject.method("void", "m1", ImmutableList.of()), isPresent());
+      assertThat(classSubject.method("void", "m2", ImmutableList.of()), not(isPresent()));
+      assertThat(classSubject.method("void", "m3", ImmutableList.of()), isPresent());
+      assertThat(classSubject.method("void", "m4", ImmutableList.of()), not(isPresent()));
+    }
+  }
+
+  private static String[] jasminCodeForPrinting(String message) {
+    return new String[] {
+      ".limit stack 2",
+      ".limit locals 1",
+      "getstatic java/lang/System/out Ljava/io/PrintStream;",
+      "ldc \"" + message + "\"",
+      "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+      "return"
+    };
+  }
+}
+
+class NonVirtualOverrideTestClass {
+
+  public static void main(String[] args) {
+    A a = new B();
+    a.m1();
+    a.m2();
+    a.m3();
+    a.m4();
+
+    a = new C();
+    a.m1();
+    a.m2();
+    a.m3();
+    a.m4();
+
+    B b = new B();
+    try {
+      b.m1();
+    } catch (IllegalAccessError exception) {
+      System.out.println("Caught IllegalAccessError when calling B.m1()");
+    }
+    try {
+      b.m3();
+    } catch (IncompatibleClassChangeError exception) {
+      System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
+    }
+
+    try {
+      b = new C();
+      b.m1();
+    } catch (IllegalAccessError exception) {
+      System.out.println("Caught IllegalAccessError when calling B.m1()");
+    }
+    try {
+      b = new C();
+      b.m3();
+    } catch (IncompatibleClassChangeError exception) {
+      System.out.println("Caught IncompatibleClassChangeError when calling B.m3()");
+    }
+
+    C c = new C();
+    c.m1();
+    c.m3();
+  }
+
+  static class A {
+
+    public void m1() {
+      System.out.println("In A.m1()");
+    }
+
+    public void m2() {
+      System.out.println("In A.m2()");
+    }
+
+    public void m3() {
+      System.out.println("In A.m3()");
+    }
+
+    public void m4() {
+      System.out.println("In A.m4()");
+    }
+  }
+
+  static class B extends A {
+
+    // Will be made private with Jasmin. This method is targeted and can therefore not be removed.
+    public void m1() {
+      System.out.println("In B.m1()");
+    }
+
+    // Will be made private with Jasmin. Ends up being dead code because the method is never called.
+    public void m2() {
+      System.out.println("In B.m2()");
+    }
+
+    // Will be made static with Jasmin. This method is targeted and can therefore not be removed.
+    public void m3() {
+      System.out.println("In B.m3()");
+    }
+
+    // Will be made static with Jasmin. Ends up being dead code because the method is never called.
+    public void m4() {
+      System.out.println("In B.m4()");
+    }
+  }
+
+  static class C extends B {
+
+    public void m1() {
+      System.out.println("In C.m1()");
+    }
+
+    public void m3() {
+      System.out.println("In C.m3()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/PrivateOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/PrivateOverrideTest.java
deleted file mode 100644
index a70d68b..0000000
--- a/src/test/java/com/android/tools/r8/shaking/PrivateOverrideTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// 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 static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ByteDataView;
-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.ProcessResult;
-import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Collection;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class PrivateOverrideTest extends TestBase {
-
-  private static Class<?> main = PrivateOverrideTestClass.class;
-  private static Class<?> A = PrivateOverrideTestClass.A.class;
-  private static Class<?> B = PrivateOverrideTestClass.B.class;
-
-  private final Backend backend;
-
-  @Rule public ExpectedException thrown = ExpectedException.none();
-
-  @Parameterized.Parameters(name = "Backend: {0}")
-  public static Collection<Backend> data() {
-    return Arrays.asList(Backend.values());
-  }
-
-  public PrivateOverrideTest(Backend backend) {
-    this.backend = backend;
-  }
-
-  @Test
-  public void test() throws Exception {
-    // Construct B such that it inherits from A and shadows method A.m() with a private method.
-    JasminBuilder jasminBuilder = new JasminBuilder();
-    ClassBuilder classBuilder = jasminBuilder.addClass(B.getName(), A.getName());
-    classBuilder.addDefaultConstructor();
-    classBuilder.addPrivateVirtualMethod(
-        "m",
-        ImmutableList.of(),
-        "V",
-        ".limit stack 2",
-        ".limit locals 1",
-        "getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "ldc \"In B.m()\"",
-        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
-        "return");
-
-    AndroidApp input =
-        AndroidApp.builder()
-            .addProgramFiles(ToolHelper.getClassFileForTestClass(main))
-            .addProgramFiles(ToolHelper.getClassFileForTestClass(A))
-            .addClassProgramData(jasminBuilder.buildClasses())
-            .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)) {
-      inputConsumer.accept(
-          ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
-          DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
-          null);
-    }
-    inputConsumer.accept(
-        ByteDataView.of(jasminBuilder.buildClasses().get(0)),
-        DescriptorUtils.javaTypeToDescriptor(B.getName()),
-        null);
-    inputConsumer.finished(null);
-
-    ProcessResult referenceResult = ToolHelper.runJava(referenceJar, main.getName());
-    assertEquals(referenceResult.exitCode, 0);
-
-    // TODO(b/116093710): Fix tree shaking.
-    thrown.expect(AssertionError.class);
-    thrown.expectMessage("java.lang.AbstractMethodError");
-    thrown.expectMessage("com.android.tools.r8.shaking.PrivateOverrideTestClass$A.m()");
-
-    // Run the program on Art after is has been compiled with R8.
-    AndroidApp compiled =
-        compileWithR8(
-            input,
-            keepMainProguardConfiguration(main),
-            options -> {
-              options.enableMinification = false;
-              options.enableVerticalClassMerging = false;
-            },
-            backend);
-    assertEquals(referenceResult.stdout, runOnVM(compiled, main, backend));
-
-    // TODO(b/116093710): Assert that B.m() is removed by tree pruner.
-  }
-}
-
-class PrivateOverrideTestClass {
-
-  public static void main(String[] args) {
-    A b = new B();
-    b.m(); // Since B.m() is made private with Jasmin, this should print "In A.m()".
-  }
-
-  static class A {
-
-    public void m() {
-      System.out.println("In A.m()");
-    }
-  }
-
-  static class B extends A {
-
-    // Will be made private with Jasmin. Therefore ends up being dead code.
-    public void m() {
-      System.out.println("In B.m()");
-    }
-  }
-}
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/tools/internal_test.py b/tools/internal_test.py
index 0cb9019..16ed783 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,39 @@
     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', '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 +127,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 +273,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..bdb1df8 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,7 @@
 # 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
+    ('gmscore', 'latest', 'deploy') # b/116575775
 ]
 
 def get_permutations():
@@ -150,8 +154,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)