Merge commit '6912a3bf054937227890bc9e52d6c5c7aaf93280' into dev-release
diff --git a/build.gradle b/build.gradle
index 1a9edec..bce1569 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,7 @@
 buildscript {
     repositories {
         maven {
-            url 'http://storage.googleapis.com/r8-deps/maven_mirror/'
+            url 'https://storage.googleapis.com/r8-deps/maven_mirror/'
         }
         mavenCentral()
         gradlePluginPortal()
@@ -54,7 +54,7 @@
 
 repositories {
     maven {
-       url 'http://storage.googleapis.com/r8-deps/maven_mirror/'
+       url 'https://storage.googleapis.com/r8-deps/maven_mirror/'
     }
     google()
     mavenCentral()
@@ -1822,7 +1822,10 @@
         result.exception.printStackTrace(processIn)
         processIn.flush()
         processIn.close()
-        process.waitFor()
+        if (process.waitFor() != 0) {
+            out.append("ERROR DURING RETRACING\n")
+            out.append(err.toString())
+        }
         out.append("\n\n--------------------------------------\n")
         out.append("OBFUSCATED STACKTRACE\n")
         out.append("--------------------------------------\n")
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 9c23fd1..63074c4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -85,7 +85,7 @@
     this.options = options;
     this.rewritePrefix = mapper;
 
-    if (enableWholeProgramOptimizations() && options.enablePropagationOfDynamicTypesAtCallSites) {
+    if (enableWholeProgramOptimizations() && options.isCallSiteOptimizationEnabled()) {
       this.callSiteOptimizationInfoPropagator =
           new CallSiteOptimizationInfoPropagator(withLiveness());
     } else {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index e2adfbb..2ee2c0c 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -136,7 +136,12 @@
         ps.println("# Annotations:");
         for (DexAnnotation annotation : annotations.annotations) {
           ps.print("#   ");
-          ps.println(annotation);
+          if (annotation.annotation.type
+              == application.dexItemFactory.createType("Lkotlin/Metadata;")) {
+            ps.println("<kotlin metadata>");
+          } else {
+            ps.println(annotation);
+          }
         }
       }
     }
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 d86a90c..d540b18 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -676,14 +676,6 @@
     return this;
   }
 
-  public boolean isProgramClass() {
-    return false;
-  }
-
-  public DexProgramClass asProgramClass() {
-    return null;
-  }
-
   public boolean isClasspathClass() {
     return false;
   }
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 82dfc12..df8a65f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -20,6 +20,14 @@
     return null;
   }
 
+  public boolean isProgramClass() {
+    return false;
+  }
+
+  public DexProgramClass asProgramClass() {
+    return null;
+  }
+
   public boolean isDexEncodedField() {
     return false;
   }
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 7b529da..7af203c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -47,7 +47,6 @@
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -1243,13 +1242,6 @@
     }
   }
 
-  public void copyMetadata(DexEncodedMethod from, OptimizationFeedback feedback) {
-    if (from.getOptimizationInfo().useIdentifierNameString()) {
-      feedback.markUseIdentifierNameString(this);
-    }
-    copyMetadata(from);
-  }
-
   private static Builder syntheticBuilder(DexEncodedMethod from) {
     return new Builder(from, true);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7800639..67a0178 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -105,7 +105,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -418,13 +417,12 @@
 
   private void desugarInterfaceMethods(
       Builder<?> builder,
-      OptimizationFeedback feedback,
       Flavor includeAllResources,
       ExecutorService executorService)
       throws ExecutionException {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(
-          builder, feedback, includeAllResources, executorService);
+          builder, includeAllResources, executorService);
     }
   }
 
@@ -460,7 +458,7 @@
 
     desugarNestBasedAccess(builder, executor);
     synthesizeLambdaClasses(builder, executor);
-    desugarInterfaceMethods(builder, simpleOptimizationFeedback, ExcludeDexResources, executor);
+    desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     synthesizeTwrCloseResourceUtilityClass(builder, executor);
     synthesizeJava8UtilityClass(builder, executor);
     processCovariantReturnTypeAnnotations(builder);
@@ -707,7 +705,7 @@
     synthesizeLambdaClasses(builder, executorService);
 
     printPhase("Interface method desugaring");
-    desugarInterfaceMethods(builder, feedback, IncludeAllResources, executorService);
+    desugarInterfaceMethods(builder, IncludeAllResources, executorService);
     feedback.updateVisibleOptimizationInfo();
 
     printPhase("Twr close resource utility class synthesis");
@@ -1100,7 +1098,10 @@
       codeRewriter.simplifyDebugLocals(code);
     }
 
-    if (!method.isProcessed()) {
+    if (method.isProcessed()) {
+      assert !appView.enableWholeProgramOptimizations()
+          || !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
+    } else {
       if (lensCodeRewriter != null) {
         timing.begin("Lens rewrite");
         lensCodeRewriter.rewrite(code, method);
@@ -1535,12 +1536,6 @@
       timing.end();
     }
 
-    // IR processing should not need to handle identifier name strings, so after the first round
-    // there should be no information for that.
-    if (methodProcessor.isPrimary()) {
-      assert !method.getOptimizationInfo().useIdentifierNameString();
-    }
-
     printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
     finalizeIR(method, code, feedback, timing);
@@ -1548,17 +1543,6 @@
     return timing;
   }
 
-  public void collectIdentifierNameStringUse(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    Iterator<Instruction> iterator = code.instructionIterator();
-    while (iterator.hasNext()) {
-      if (iterator.next().isDexItemBasedConstString()) {
-        feedback.markUseIdentifierNameString(method);
-        break;
-      }
-    }
-  }
-
   // Compute optimization info summary for the current method unless it is pinned
   // (in that case we should not be making any assumptions about the behavior of the method).
   public void collectOptimizationInfo(
@@ -1575,7 +1559,6 @@
 
   public void finalizeIR(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
-    collectIdentifierNameStringUse(method, code, feedback);
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
       finalizeToCf(method, code, feedback);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 7d4a203..a15cbe4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -51,8 +51,6 @@
 
   void markAsPropagated(DexEncodedMethod method);
 
-  void markUseIdentifierNameString(DexEncodedMethod method);
-
   void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
 
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 7e0c9d5..96c93a9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -21,9 +21,11 @@
 import java.util.Deque;
 import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
 
 class PostMethodProcessor implements MethodProcessor {
 
@@ -87,6 +89,13 @@
     PostMethodProcessor build(
         AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
         throws ExecutionException {
+      if (!appView.appInfo().reprocess.isEmpty()) {
+        put(
+            appView.appInfo().reprocess.stream()
+                .map(appView::definitionFor)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet()));
+      }
       if (methodsMap.keySet().isEmpty()) {
         // Nothing to revisit.
         return null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 21b182e..6a79659 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -38,7 +38,6 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
 import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLense;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.position.MethodPosition;
@@ -914,7 +913,6 @@
    */
   public void desugarInterfaceMethods(
       Builder<?> builder,
-      OptimizationFeedback feedback,
       Flavor flavour,
       ExecutorService executorService)
       throws ExecutionException {
@@ -933,7 +931,7 @@
     // classes if needed.
     AppInfo appInfo = appView.appInfo();
     for (Entry<DexType, DexProgramClass> entry :
-        processInterfaces(builder, feedback, flavour).entrySet()) {
+        processInterfaces(builder, flavour).entrySet()) {
       // Don't need to optimize synthesized class since all of its methods
       // are just moved from interfaces and don't need to be re-processed.
       DexProgramClass synthesizedClass = entry.getValue();
@@ -964,9 +962,9 @@
   }
 
   private Map<DexType, DexProgramClass> processInterfaces(
-      Builder<?> builder, OptimizationFeedback feedback, Flavor flavour) {
+      Builder<?> builder, Flavor flavour) {
     NestedGraphLense.Builder graphLensBuilder = InterfaceProcessorNestedGraphLense.builder();
-    InterfaceProcessor processor = new InterfaceProcessor(appView, this, feedback);
+    InterfaceProcessor processor = new InterfaceProcessor(appView, this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, true)) {
         processor.process(clazz, graphLensBuilder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index ef9b9c9..04ce705 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -31,7 +31,6 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -57,16 +56,14 @@
 
   private final AppView<?> appView;
   private final InterfaceMethodRewriter rewriter;
-  private final OptimizationFeedback feedback;
 
   // All created companion and dispatch classes indexed by interface type.
   final Map<DexType, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
 
   InterfaceProcessor(
-      AppView<?> appView, InterfaceMethodRewriter rewriter, OptimizationFeedback feedback) {
+      AppView<?> appView, InterfaceMethodRewriter rewriter) {
     this.appView = appView;
     this.rewriter = rewriter;
-    this.feedback = feedback;
   }
 
   void process(DexProgramClass iface, NestedGraphLense.Builder graphLensBuilder) {
@@ -99,7 +96,7 @@
             code, companionMethod.getArity(), appView);
         DexEncodedMethod implMethod = new DexEncodedMethod(
             companionMethod, newFlags, virtual.annotations, virtual.parameterAnnotationsList, code, true);
-        implMethod.copyMetadata(virtual, feedback);
+        implMethod.copyMetadata(virtual);
         virtual.setDefaultInterfaceMethodImplementation(implMethod);
         companionMethods.add(implMethod);
         graphLensBuilder.move(virtual.method, implMethod.method);
@@ -141,7 +138,7 @@
                 direct.parameterAnnotationsList,
                 direct.getCode(),
                 true);
-        implMethod.copyMetadata(direct, feedback);
+        implMethod.copyMetadata(direct);
         companionMethods.add(implMethod);
         graphLensBuilder.move(oldMethod, companionMethod);
       } else {
@@ -168,7 +165,7 @@
                   direct.parameterAnnotationsList,
                   code,
                   true);
-          implMethod.copyMetadata(direct, feedback);
+          implMethod.copyMetadata(direct);
           companionMethods.add(implMethod);
           graphLensBuilder.move(oldMethod, companionMethod);
         } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 00c797d..627aec5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -155,6 +155,9 @@
   // can avoid recording arguments for the same method accidentally.
   private void recordArgumentsIfNecessary(DexEncodedMethod target, List<Value> inValues) {
     assert !target.isObsolete();
+    if (appView.appInfo().neverReprocess.contains(target.method)) {
+      return;
+    }
     if (target.getCallSiteOptimizationInfo().isTop()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index eff0d07..a63cb59 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -11,8 +11,10 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -85,7 +87,8 @@
   }
 
   public void canonicalize(AppView<?> appView, IRCode code) {
-    DexType context = code.method.method.holder;
+    DexEncodedMethod method = code.method;
+    DexType context = method.method.holder;
     Object2ObjectLinkedOpenCustomHashMap<Instruction, List<Value>> valuesDefinedByConstant =
         new Object2ObjectLinkedOpenCustomHashMap<>(
             new Strategy<Instruction>() {
@@ -145,6 +148,13 @@
         if (!abstractValue.isSingleFieldValue()) {
           continue;
         }
+        SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
+        if (method.isClassInitializer()
+            && method.method.holder == singleFieldValue.getField().holder) {
+          // Avoid that canonicalization inserts a read before the unique write in the class
+          // initializer, as that would change the program behavior.
+          continue;
+        }
         if (current.instructionMayHaveSideEffects(appView, context)) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 06a94a6..36ccecf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -28,7 +28,6 @@
   static AbstractValue UNKNOWN_ABSTRACT_RETURN_VALUE = UnknownValue.getInstance();
   static TypeLatticeElement UNKNOWN_TYPE = null;
   static ClassTypeLatticeElement UNKNOWN_CLASS_TYPE = null;
-  static boolean DOES_NOT_USE_IDENTIFIER_NAME_STRING = false;
   static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
   static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
   static ClassInlinerEligibilityInfo UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
@@ -149,11 +148,6 @@
   }
 
   @Override
-  public boolean useIdentifierNameString() {
-    return DOES_NOT_USE_IDENTIFIER_NAME_STRING;
-  }
-
-  @Override
   public boolean forceInline() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index fd8078d..46749fd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -68,8 +68,6 @@
 
   boolean neverInline();
 
-  boolean useIdentifierNameString();
-
   boolean checksNullReceiverBeforeAnySideEffect();
 
   boolean triggersClassInitBeforeAnySideEffect();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 330eba6..27f8be0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -210,11 +210,6 @@
   }
 
   @Override
-  public synchronized void markUseIdentifierNameString(DexEncodedMethod method) {
-    getMethodOptimizationInfoForUpdating(method).markUseIdentifierNameString();
-  }
-
-  @Override
   public synchronized void markCheckNullReceiverBeforeAnySideEffect(
       DexEncodedMethod method, boolean mark) {
     getMethodOptimizationInfoForUpdating(method).markCheckNullReceiverBeforeAnySideEffect(mark);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 2495e58..8d6e8c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -99,9 +99,6 @@
   public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {}
 
   @Override
-  public void markUseIdentifierNameString(DexEncodedMethod method) {}
-
-  @Override
   public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 34a1fb8..65ea424 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -140,11 +140,6 @@
   }
 
   @Override
-  public void markUseIdentifierNameString(DexEncodedMethod method) {
-    method.getMutableOptimizationInfo().markUseIdentifierNameString();
-  }
-
-  @Override
   public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
     // Ignored.
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index def162e..dc67349 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -70,7 +70,7 @@
   private static final int RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG = 0x10;
   private static final int NEVER_RETURNS_NULL_FLAG = 0x20;
   private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
-  private static final int USE_IDENTIFIER_NAME_STRING_FLAG = 0x80;
+  private static final int UNUSED_FLAG = 0x80;
   private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100;
   private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200;
   private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
@@ -98,9 +98,7 @@
         BooleanUtils.intValue(defaultOptInfo.neverReturnsNull()) * NEVER_RETURNS_NULL_FLAG;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
-    defaultFlags |=
-        BooleanUtils.intValue(defaultOptInfo.useIdentifierNameString())
-            * USE_IDENTIFIER_NAME_STRING_FLAG;
+    defaultFlags |= 0 * UNUSED_FLAG;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect())
             * CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG;
@@ -294,11 +292,6 @@
   }
 
   @Override
-  public boolean useIdentifierNameString() {
-    return isFlagSet(USE_IDENTIFIER_NAME_STRING_FLAG);
-  }
-
-  @Override
   public boolean forceInline() {
     return inlining == InlinePreference.ForceInline;
   }
@@ -436,11 +429,6 @@
     inlining = InlinePreference.NeverInline;
   }
 
-  // TODO(b/140214568): Should be package-private.
-  public void markUseIdentifierNameString() {
-    setFlag(USE_IDENTIFIER_NAME_STRING_FLAG);
-  }
-
   void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
     setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
   }
@@ -492,7 +480,6 @@
     //   * returnsObjectWithLowerBoundType
     // inlining: it is not inlined, and thus staticized. No more chance of inlining, though.
     inlining = InlinePreference.Default;
-    // useIdentifierNameString: code is not changed.
     // checksNullReceiverBeforeAnySideEffect: no more receiver.
     markCheckNullReceiverBeforeAnySideEffect(
         DefaultMethodOptimizationInfo.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 1d3718b..fd5df23 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -14,6 +14,7 @@
 import java.io.StringWriter;
 import java.io.Writer;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -214,6 +215,10 @@
     this.mappedNamingsByName = mappedNamingsByName;
   }
 
+  public MappedRangesOfName getMappedRangesForRenamedName(String renamedName) {
+    return mappedRangesByRenamedName.get(renamedName);
+  }
+
   @Override
   public MemberNaming lookup(Signature renamedSignature) {
     if (renamedSignature.kind() == SignatureKind.METHOD) {
@@ -276,6 +281,10 @@
     }
   }
 
+  public Collection<MemberNaming> allFieldNamings() {
+    return fieldMembers.values();
+  }
+
   @Override
   public <T extends Throwable> void forAllMethodNaming(
       ThrowingConsumer<MemberNaming, T> consumer) throws T {
@@ -284,6 +293,10 @@
     }
   }
 
+  public Collection<MemberNaming> allMethodNamings() {
+    return methodMembers.values();
+  }
+
   void write(Writer writer) throws IOException {
     writer.append(originalName);
     writer.append(" -> ");
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 481a46c..72d5558 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.utils.ThreadUtils;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
@@ -150,15 +149,6 @@
   }
 
   private void replaceDexItemBasedConstStringInMethod(DexEncodedMethod encodedMethod) {
-    if (!encodedMethod.getOptimizationInfo().useIdentifierNameString()) {
-      assert (encodedMethod.getCode().isDexCode()
-              && Arrays.stream(encodedMethod.getCode().asDexCode().instructions)
-                  .noneMatch(Instruction::isDexItemBasedConstString))
-          || (encodedMethod.getCode().isCfCode()
-              && encodedMethod.getCode().asCfCode().instructions.stream()
-                  .noneMatch(CfInstruction::isDexItemBasedConstString));
-      return;
-    }
     Code code = encodedMethod.getCode();
     assert code != null;
     if (code.isDexCode()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 89cf63c..a2a3380 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -139,6 +139,10 @@
   public final Set<DexMethod> keepConstantArguments;
   /** All methods that may not have any unused arguments removed. */
   public final Set<DexMethod> keepUnusedArguments;
+  /** All methods that must be reprocessed (testing only). */
+  public final Set<DexMethod> reprocess;
+  /** All methods that must not be reprocessed (testing only). */
+  public final Set<DexMethod> neverReprocess;
   /** All types that should be inlined if possible due to a configuration directive. */
   public final PredicateSet<DexType> alwaysClassInline;
   /** All types that *must* never be inlined due to a configuration directive (testing only). */
@@ -208,6 +212,8 @@
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
+      Set<DexMethod> reprocess,
+      Set<DexMethod> neverReprocess,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
       Set<DexType> neverMerge,
@@ -246,6 +252,8 @@
     this.whyAreYouNotInlining = whyAreYouNotInlining;
     this.keepConstantArguments = keepConstantArguments;
     this.keepUnusedArguments = keepUnusedArguments;
+    this.reprocess = reprocess;
+    this.neverReprocess = neverReprocess;
     this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
     this.neverMerge = neverMerge;
@@ -287,6 +295,8 @@
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
+      Set<DexMethod> reprocess,
+      Set<DexMethod> neverReprocess,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
       Set<DexType> neverMerge,
@@ -325,6 +335,8 @@
     this.whyAreYouNotInlining = whyAreYouNotInlining;
     this.keepConstantArguments = keepConstantArguments;
     this.keepUnusedArguments = keepUnusedArguments;
+    this.reprocess = reprocess;
+    this.neverReprocess = neverReprocess;
     this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
     this.neverMerge = neverMerge;
@@ -367,6 +379,8 @@
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
+        previous.reprocess,
+        previous.neverReprocess,
         previous.alwaysClassInline,
         previous.neverClassInline,
         previous.neverMerge,
@@ -416,6 +430,8 @@
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
+        previous.reprocess,
+        previous.neverReprocess,
         previous.alwaysClassInline,
         previous.neverClassInline,
         previous.neverMerge,
@@ -492,6 +508,8 @@
         lense.rewriteMethodsWithRenamedSignature(previous.keepConstantArguments);
     this.keepUnusedArguments =
         lense.rewriteMethodsWithRenamedSignature(previous.keepUnusedArguments);
+    this.reprocess = lense.rewriteMethodsWithRenamedSignature(previous.reprocess);
+    this.neverReprocess = lense.rewriteMethodsWithRenamedSignature(previous.neverReprocess);
     assert lense.assertDefinitionsNotModified(
         previous.neverMerge.stream()
             .map(this::definitionFor)
@@ -547,6 +565,8 @@
     this.whyAreYouNotInlining = previous.whyAreYouNotInlining;
     this.keepConstantArguments = previous.keepConstantArguments;
     this.keepUnusedArguments = previous.keepUnusedArguments;
+    this.reprocess = previous.reprocess;
+    this.neverReprocess = previous.neverReprocess;
     this.alwaysClassInline = previous.alwaysClassInline;
     this.neverClassInline = previous.neverClassInline;
     this.neverMerge = previous.neverMerge;
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 cd9db29..f8cdb36 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -88,6 +88,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -2083,10 +2084,7 @@
       return;
     }
 
-    // TODO(mkroghj): Remove pinnedItems check here.
-    if (instantiatedTypes.contains(clazz)
-        || instantiatedInterfaceTypes.contains(clazz)
-        || pinnedItems.contains(clazz.type)) {
+    if (instantiatedTypes.contains(clazz) || instantiatedInterfaceTypes.contains(clazz)) {
       markVirtualMethodAsLive(
           clazz,
           encodedPossibleTarget,
@@ -2250,7 +2248,23 @@
     this.rootSet = rootSet;
     this.dontWarnPatterns = dontWarnPatterns;
     // Translate the result of root-set computation into enqueuer actions.
-    enqueueRootItems(rootSet.noShrinking);
+    if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
+      enqueueRootItems(rootSet.noShrinking);
+    } else {
+      // Add everything if we are not shrinking.
+      assert appView.options().getProguardConfiguration().getKeepAllRule() != null;
+      ImmutableSet<ProguardKeepRuleBase> keepAllSet =
+          ImmutableSet.of(appView.options().getProguardConfiguration().getKeepAllRule());
+      for (DexProgramClass dexProgramClass : appView.appInfo().classes()) {
+        for (DexEncodedMethod method : dexProgramClass.methods()) {
+          this.enqueueRootItem(method, keepAllSet);
+        }
+        for (DexEncodedField field : dexProgramClass.fields()) {
+          this.enqueueRootItem(field, keepAllSet);
+        }
+        this.enqueueRootItem(dexProgramClass, keepAllSet);
+      }
+    }
     trace(executorService, timing);
     options.reporter.failIfPendingErrors();
     finalizeLibraryMethodOverrideInformation();
@@ -2332,6 +2346,8 @@
             rootSet.whyAreYouNotInlining,
             rootSet.keepConstantArguments,
             rootSet.keepUnusedArguments,
+            rootSet.reprocess,
+            rootSet.neverReprocess,
             rootSet.alwaysClassInline,
             rootSet.neverClassInline,
             rootSet.neverMerge,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 3c91bc8..8b86596 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -71,6 +71,7 @@
     private boolean configurationDebugging = false;
     private boolean dontUseMixedCaseClassnames = false;
     private int maxRemovedAndroidLogLevel = 1;
+    private ProguardKeepRule keepAllRule;
 
     private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
       this.dexItemFactory = dexItemFactory;
@@ -356,7 +357,8 @@
               keepDirectories.build(),
               configurationDebugging,
               dontUseMixedCaseClassnames,
-              maxRemovedAndroidLogLevel);
+              maxRemovedAndroidLogLevel,
+              keepAllRule);
 
       reporter.failIfPendingErrors();
 
@@ -372,11 +374,15 @@
       // shrinking or minification is turned off through the API, then add a match all rule
       // which will apply that.
       if (!isShrinking() || !isObfuscating() || !isOptimizing()) {
-        addRule(ProguardKeepRule.defaultKeepAllRule(modifiers -> {
-          modifiers.setAllowsShrinking(isShrinking());
-          modifiers.setAllowsOptimization(isOptimizing());
-          modifiers.setAllowsObfuscation(isObfuscating());
-        }));
+        ProguardKeepRule rule =
+            ProguardKeepRule.defaultKeepAllRule(
+                modifiers -> {
+                  modifiers.setAllowsShrinking(isShrinking());
+                  modifiers.setAllowsOptimization(isOptimizing());
+                  modifiers.setAllowsObfuscation(isObfuscating());
+                });
+        addRule(rule);
+        this.keepAllRule = rule;
       }
 
       if (keepRuleSynthesisForRecompilation) {
@@ -426,6 +432,7 @@
   private final boolean configurationDebugging;
   private final boolean dontUseMixedCaseClassnames;
   private final int maxRemovedAndroidLogLevel;
+  private final ProguardKeepRule keepAllRule;
 
   private ProguardConfiguration(
       String parsedConfiguration,
@@ -466,7 +473,8 @@
       ProguardPathFilter keepDirectories,
       boolean configurationDebugging,
       boolean dontUseMixedCaseClassnames,
-      int maxRemovedAndroidLogLevel) {
+      int maxRemovedAndroidLogLevel,
+      ProguardKeepRule keepAllRule) {
     this.parsedConfiguration = parsedConfiguration;
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
@@ -506,6 +514,7 @@
     this.configurationDebugging = configurationDebugging;
     this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
     this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+    this.keepAllRule = keepAllRule;
   }
 
   /**
@@ -676,6 +685,10 @@
     return maxRemovedAndroidLogLevel;
   }
 
+  public ProguardKeepRule getKeepAllRule() {
+    return keepAllRule;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
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 cc05e23..f1a2b0f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -472,6 +472,20 @@
           configurationBuilder.addRule(rule);
           return true;
         }
+        if (acceptString("neverreprocessmethod")) {
+          configurationBuilder.addRule(
+              parseReprocessMethodRule(ReprocessMethodRule.Type.NEVER, optionStart));
+          return true;
+        }
+        if (acceptString("reprocessclassinitializer")) {
+          configurationBuilder.addRule(parseReprocessClassInitializerRule(optionStart));
+          return true;
+        }
+        if (acceptString("reprocessmethod")) {
+          configurationBuilder.addRule(
+              parseReprocessMethodRule(ReprocessMethodRule.Type.ALWAYS, optionStart));
+          return true;
+        }
         if (acceptString("whyareyounotinlining")) {
           WhyAreYouNotInliningRule rule = parseWhyAreYouNotInliningRule(optionStart);
           configurationBuilder.addRule(rule);
@@ -807,6 +821,28 @@
       return keepRuleBuilder.build();
     }
 
+    private ReprocessClassInitializerRule parseReprocessClassInitializerRule(Position start)
+        throws ProguardRuleParserException {
+      ReprocessClassInitializerRule.Builder builder =
+          ReprocessClassInitializerRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(builder, false);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
+      return builder.build();
+    }
+
+    private ReprocessMethodRule parseReprocessMethodRule(
+        ReprocessMethodRule.Type type, Position start) throws ProguardRuleParserException {
+      ReprocessMethodRule.Builder builder =
+          ReprocessMethodRule.builder().setOrigin(origin).setStart(start).setType(type);
+      parseClassSpec(builder, false);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
+      return builder.build();
+    }
+
     private WhyAreYouNotInliningRule parseWhyAreYouNotInliningRule(Position start)
         throws ProguardRuleParserException {
       WhyAreYouNotInliningRule.Builder keepRuleBuilder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index f899a6b..41aa9c5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -65,6 +65,14 @@
     return null;
   }
 
+  public boolean isReprocessMethodRule() {
+    return false;
+  }
+
+  public ReprocessMethodRule asReprocessMethodRule() {
+    return null;
+  }
+
   Iterable<DexProgramClass> relevantCandidatesForRule(
       AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> defaultValue) {
     if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
new file mode 100644
index 0000000..fa7d352
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ReprocessClassInitializerRule extends ProguardConfigurationRule {
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ReprocessClassInitializerRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public ReprocessClassInitializerRule build() {
+      return new ReprocessClassInitializerRule(
+          origin,
+          getPosition(),
+          source,
+          classAnnotation,
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          inheritanceAnnotation,
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  private ReprocessClassInitializerRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotation,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotation,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return "reprocessclassinitializer";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java b/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
new file mode 100644
index 0000000..8e8e288
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ReprocessMethodRule extends ProguardConfigurationRule {
+
+  public enum Type {
+    ALWAYS,
+    NEVER
+  }
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ReprocessMethodRule, Builder> {
+
+    private Type type;
+
+    private Builder() {
+      super();
+    }
+
+    public Builder setType(Type type) {
+      this.type = type;
+      return this;
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public ReprocessMethodRule build() {
+      return new ReprocessMethodRule(
+          origin,
+          getPosition(),
+          source,
+          classAnnotation,
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          inheritanceAnnotation,
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules,
+          type);
+    }
+  }
+
+  private final Type type;
+
+  private ReprocessMethodRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules,
+      Type type) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotation,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotation,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+    this.type = type;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  @Override
+  public boolean isReprocessMethodRule() {
+    return true;
+  }
+
+  @Override
+  public ReprocessMethodRule asReprocessMethodRule() {
+    return this;
+  }
+
+  @Override
+  String typeString() {
+    switch (type) {
+      case ALWAYS:
+        return "reprocessmethod";
+      case NEVER:
+        return "neverreprocessmethod";
+      default:
+        throw new Unreachable();
+    }
+  }
+}
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 1cfd120..4186091 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -83,6 +83,8 @@
   private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
+  private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
+  private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
   private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
   private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
@@ -213,16 +215,15 @@
         markMatchingOverriddenMethods(
             appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
         markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-      } else if (rule instanceof ClassMergingRule) {
-        if (allRulesSatisfied(memberKeepRules, clazz)) {
-          markClass(clazz, rule, ifRule);
-        }
       } else if (rule instanceof InlineRule
           || rule instanceof ConstantArgumentRule
           || rule instanceof UnusedArgumentRule
+          || rule instanceof ReprocessMethodRule
           || rule instanceof WhyAreYouNotInliningRule) {
         markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
-      } else if (rule instanceof ClassInlineRule) {
+      } else if (rule instanceof ClassInlineRule
+          || rule instanceof ClassMergingRule
+          || rule instanceof ReprocessClassInitializerRule) {
         if (allRulesSatisfied(memberKeepRules, clazz)) {
           markClass(clazz, rule, ifRule);
         }
@@ -319,6 +320,8 @@
         whyAreYouNotInlining,
         keepParametersWithConstantValue,
         keepUnusedArguments,
+        reprocess,
+        neverReprocess,
         alwaysClassInline,
         neverClassInline,
         neverMerge,
@@ -1184,6 +1187,27 @@
         keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
         context.markAsUsed();
       }
+    } else if (context instanceof ReprocessClassInitializerRule) {
+      DexProgramClass clazz = item.asProgramClass();
+      if (clazz != null && clazz.hasClassInitializer()) {
+        reprocess.add(clazz.getClassInitializer().method);
+        context.markAsUsed();
+      }
+    } else if (context.isReprocessMethodRule()) {
+      if (item.isDexEncodedMethod()) {
+        DexEncodedMethod method = item.asDexEncodedMethod();
+        switch (context.asReprocessMethodRule().getType()) {
+          case ALWAYS:
+            reprocess.add(method.method);
+            break;
+          case NEVER:
+            neverReprocess.add(method.method);
+            break;
+          default:
+            throw new Unreachable();
+        }
+        context.markAsUsed();
+      }
     } else if (context instanceof UnusedArgumentRule) {
       if (item.isDexEncodedMethod()) {
         keepUnusedArguments.add(item.asDexEncodedMethod().method);
@@ -1208,6 +1232,8 @@
     public final Set<DexMethod> whyAreYouNotInlining;
     public final Set<DexMethod> keepConstantArguments;
     public final Set<DexMethod> keepUnusedArguments;
+    public final Set<DexMethod> reprocess;
+    public final Set<DexMethod> neverReprocess;
     public final PredicateSet<DexType> alwaysClassInline;
     public final Set<DexType> neverClassInline;
     public final Set<DexType> neverMerge;
@@ -1235,6 +1261,8 @@
         Set<DexMethod> whyAreYouNotInlining,
         Set<DexMethod> keepConstantArguments,
         Set<DexMethod> keepUnusedArguments,
+        Set<DexMethod> reprocess,
+        Set<DexMethod> neverReprocess,
         PredicateSet<DexType> alwaysClassInline,
         Set<DexType> neverClassInline,
         Set<DexType> neverMerge,
@@ -1259,6 +1287,8 @@
       this.whyAreYouNotInlining = whyAreYouNotInlining;
       this.keepConstantArguments = keepConstantArguments;
       this.keepUnusedArguments = keepUnusedArguments;
+      this.reprocess = reprocess;
+      this.neverReprocess = neverReprocess;
       this.alwaysClassInline = alwaysClassInline;
       this.neverClassInline = neverClassInline;
       this.neverMerge = Collections.unmodifiableSet(neverMerge);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d0a4fb1..5b0400f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1134,6 +1134,15 @@
     return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.N);
   }
 
+  public boolean isCallSiteOptimizationEnabled() {
+    return enablePropagationOfConstantsAtCallSites || enablePropagationOfDynamicTypesAtCallSites;
+  }
+
+  public void disableCallSiteOptimization() {
+    enablePropagationOfConstantsAtCallSites = false;
+    enablePropagationOfDynamicTypesAtCallSites = false;
+  }
+
   public boolean isInterfaceMethodDesugaringEnabled() {
     // This condition is to filter out tests that never set program consumer.
     if (!hasConsumer()) {
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
new file mode 100644
index 0000000..64c44b5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.function.Predicate;
+
+public class IterableUtils {
+
+  public static <T> int firstIndexMatching(Iterable<T> iterable, Predicate<T> tester) {
+    int i = 0;
+    for (T element : iterable) {
+      if (tester.test(element)) {
+        return i;
+      }
+      i++;
+    }
+    return -1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/NeverReprocessMethod.java b/src/test/java/com/android/tools/r8/NeverReprocessMethod.java
new file mode 100644
index 0000000..ffbfe5a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NeverReprocessMethod.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverReprocessMethod {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index d80f7cd..f847f34 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -36,6 +36,7 @@
 
   enum AllowedDiagnosticMessages {
     ALL,
+    ERROR,
     INFO,
     NONE,
     WARNING
@@ -47,13 +48,16 @@
 
   private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
   private boolean allowUnusedProguardConfigurationRules = false;
-  private boolean enableInliningAnnotations = false;
-  private boolean enableNeverClassInliningAnnotations = false;
-  private boolean enableMergeAnnotations = false;
-  private boolean enableMemberValuePropagationAnnotations = false;
   private boolean enableConstantArgumentAnnotations = false;
-  private boolean enableUnusedArgumentAnnotations = false;
+  private boolean enableInliningAnnotations = false;
+  private boolean enableMemberValuePropagationAnnotations = false;
+  private boolean enableMergeAnnotations = false;
+  private boolean enableNeverClassInliningAnnotations = false;
+  private boolean enableNeverReprocessMethodAnnotations = false;
+  private boolean enableReprocessClassInitializerAnnotations = false;
+  private boolean enableReprocessMethodAnnotations = false;
   private boolean enableSideEffectAnnotations = false;
+  private boolean enableUnusedArgumentAnnotations = false;
   private CollectingGraphConsumer graphConsumer = null;
   private List<String> keepRules = new ArrayList<>();
   private List<Path> mainDexRulesFiles = new ArrayList<>();
@@ -63,13 +67,16 @@
   R8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
-    if (enableInliningAnnotations
-        || enableNeverClassInliningAnnotations
-        || enableMergeAnnotations
+    if (enableConstantArgumentAnnotations
+        || enableInliningAnnotations
         || enableMemberValuePropagationAnnotations
-        || enableConstantArgumentAnnotations
-        || enableUnusedArgumentAnnotations
-        || enableSideEffectAnnotations) {
+        || enableMergeAnnotations
+        || enableNeverClassInliningAnnotations
+        || enableNeverReprocessMethodAnnotations
+        || enableReprocessClassInitializerAnnotations
+        || enableReprocessMethodAnnotations
+        || enableSideEffectAnnotations
+        || enableUnusedArgumentAnnotations) {
       ToolHelper.allowTestProguardOptions(builder);
     }
     if (!keepRules.isEmpty()) {
@@ -131,6 +138,9 @@
       case ALL:
         compileResult.assertDiagnosticMessageThatMatches(new IsAnything<>());
         break;
+      case ERROR:
+        compileResult.assertOnlyErrors();
+        break;
       case INFO:
         compileResult.assertOnlyInfos();
         break;
@@ -247,6 +257,12 @@
     return addOptionsModification(options -> options.testing.allowClassInlinerGracefulExit = true);
   }
 
+  /**
+   * Allow info, warning, and error diagnostics.
+   *
+   * <p>This should only be used if a test has any of these diagnostic messages. Therefore, it is a
+   * failure if no such diagnostics are reported.
+   */
   public T allowDiagnosticMessages() {
     assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE;
     allowedDiagnosticMessages = AllowedDiagnosticMessages.ALL;
@@ -257,6 +273,12 @@
     return allowDiagnosticInfoMessages(true);
   }
 
+  /**
+   * Allow info diagnostics if {@param condition} is true.
+   *
+   * <p>This should only be used if a test has at least one diagnostic info message. Therefore, it
+   * is a failure if no such diagnostics are reported.
+   */
   public T allowDiagnosticInfoMessages(boolean condition) {
     if (condition) {
       assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE;
@@ -269,6 +291,12 @@
     return allowDiagnosticWarningMessages(true);
   }
 
+  /**
+   * Allow warning diagnostics if {@param condition} is true.
+   *
+   * <p>This should only be used if a test has at least one diagnostic warning message. Therefore,
+   * it is a failure if no such diagnostics are reported.
+   */
   public T allowDiagnosticWarningMessages(boolean condition) {
     if (condition) {
       assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE;
@@ -277,6 +305,24 @@
     return self();
   }
 
+  public T allowDiagnosticErrorMessages() {
+    return allowDiagnosticErrorMessages(true);
+  }
+
+  /**
+   * Allow error diagnostics if {@param condition} is true.
+   *
+   * <p>This should only be used if a test has at least one diagnostic error message. Therefore, it
+   * is a failure if no such diagnostics are reported.
+   */
+  public T allowDiagnosticErrorMessages(boolean condition) {
+    if (condition) {
+      assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE;
+      allowedDiagnosticMessages = AllowedDiagnosticMessages.ERROR;
+    }
+    return self();
+  }
+
   public T allowUnusedProguardConfigurationRules() {
     return allowUnusedProguardConfigurationRules(true);
   }
@@ -352,6 +398,35 @@
     return self();
   }
 
+  public T enableReprocessClassInitializerAnnotations() {
+    if (!enableReprocessClassInitializerAnnotations) {
+      enableReprocessClassInitializerAnnotations = true;
+      addInternalKeepRules(
+          "-reprocessclassinitializer @com.android.tools.r8.ReprocessClassInitializer class *");
+    }
+    return self();
+  }
+
+  public T enableReprocessMethodAnnotations() {
+    if (!enableReprocessMethodAnnotations) {
+      enableReprocessMethodAnnotations = true;
+      addInternalKeepRules(
+          "-reprocessmethod class * {", "  @com.android.tools.r8.ReprocessMethod <methods>;", "}");
+    }
+    return self();
+  }
+
+  public T enableNeverReprocessMethodAnnotations() {
+    if (!enableNeverReprocessMethodAnnotations) {
+      enableNeverReprocessMethodAnnotations = true;
+      addInternalKeepRules(
+          "-neverreprocessmethod class * {",
+          "  @com.android.tools.r8.NeverReprocessMethod <methods>;",
+          "}");
+    }
+    return self();
+  }
+
   public T enableSideEffectAnnotations() {
     if (!enableSideEffectAnnotations) {
       enableSideEffectAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/ReprocessClassInitializer.java b/src/test/java/com/android/tools/r8/ReprocessClassInitializer.java
new file mode 100644
index 0000000..0b9c34a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ReprocessClassInitializer.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface ReprocessClassInitializer {}
diff --git a/src/test/java/com/android/tools/r8/ReprocessMethod.java b/src/test/java/com/android/tools/r8/ReprocessMethod.java
new file mode 100644
index 0000000..049d49b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ReprocessMethod.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface ReprocessMethod {}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 6ccae1f..3d55195 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -10,43 +10,46 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.StringConsumer.FileConsumer;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.MoveException;
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
 import com.android.tools.r8.debug.DexDebugTestConfig;
-import com.android.tools.r8.graph.DexCode;
 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.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidApp.Builder;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.base.Joiner;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Streams;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -57,12 +60,15 @@
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.stream.IntStream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 // TODO(christofferqa): Add tests to check that statically typed invocations on method handles
 // continue to work after class merging.
-@RunWith(VmTestRunner.class)
+@RunWith(Parameterized.class)
 public class VerticalClassMergerTest extends TestBase {
 
   private static final Path CF_DIR =
@@ -78,6 +84,17 @@
   private static final Path DONT_OPTIMIZE = Paths.get(ToolHelper.EXAMPLES_DIR)
       .resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
 
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public VerticalClassMergerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   private void configure(InternalOptions options) {
     options.enableSideEffectAnalysis = false;
     options.enableUnusedInterfaceRemoval = false;
@@ -87,12 +104,13 @@
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException, CompilationFailedException {
     inspector =
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addProgramFiles(EXAMPLE_JAR)
             .addKeepRuleFiles(proguardConfig)
             .enableProguardTestOptions()
             .noMinification()
             .addOptionsModification(optionsConsumer)
+            .setMinApi(parameters.getApiLevel())
             .compile()
             .inspector();
   }
@@ -141,7 +159,7 @@
             "classmerging.ArrayTypeCollisionTest$A",
             "classmerging.ArrayTypeCollisionTest$B");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(
                 getProguardConfig(
                     EXAMPLE_KEEP,
@@ -203,7 +221,7 @@
 
     // Run test.
     runTestOnInput(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(
                 "-keep class " + main + " {",
                 "  public static void main(...);",
@@ -237,7 +255,7 @@
         ImmutableSet.of("classmerging.CallGraphCycleTest", "classmerging.CallGraphCycleTest$B");
     for (int i = 0; i < 5; i++) {
       runTest(
-          testForR8(Backend.DEX)
+          testForR8(parameters.getBackend())
               .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
               .allowUnusedProguardConfigurationRules(),
           main,
@@ -261,19 +279,20 @@
             "classmerging.ConflictInGeneratedNameTest$B");
     CodeInspector inspector =
         runTestOnInput(
-            testForR8(Backend.DEX)
-                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-                .allowUnusedProguardConfigurationRules(),
-            main,
-            readProgramFiles(programFiles),
-            preservedClassNames::contains,
-            options -> {
-              configure(options);
-              // Avoid that direct methods in B get inlined.
-              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-            },
-            // Disable debug testing since the test has a method with "$classmerging$" in the name.
-            null);
+                testForR8(parameters.getBackend())
+                    .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                    .addOptionsModification(this::configure)
+                    .addOptionsModification(
+                        options ->
+                            options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+                    .allowUnusedProguardConfigurationRules(),
+                main,
+                readProgramFiles(programFiles),
+                preservedClassNames::contains,
+                // Disable debug testing since the test has a method with "$classmerging$" in the
+                // name.
+                null)
+            .inspector();
 
     ClassSubject clazzSubject = inspector.clazz("classmerging.ConflictInGeneratedNameTest$B");
     assertThat(clazzSubject, isPresent());
@@ -335,7 +354,7 @@
             "classmerging.FieldCollisionTest",
             "classmerging.FieldCollisionTest$B");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -361,16 +380,14 @@
             "classmerging.LambdaRewritingTest$FunctionImpl",
             "classmerging.LambdaRewritingTest$InterfaceImpl");
     runTestOnInput(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
+            .addOptionsModification(this::configure)
+            .addOptionsModification(options -> options.enableClassInlining = false)
             .allowUnusedProguardConfigurationRules(),
         main,
         readProgramFiles(programFiles),
-        name -> preservedClassNames.contains(name) || name.contains("$Lambda$"),
-        options -> {
-          this.configure(options);
-          options.enableClassInlining = false;
-        });
+        name -> preservedClassNames.contains(name) || name.contains("$Lambda$"));
   }
 
   @Test
@@ -388,16 +405,14 @@
             "classmerging.ConflictingInterfaceSignaturesTest",
             "classmerging.ConflictingInterfaceSignaturesTest$InterfaceImpl");
     runTestOnInput(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .addOptionsModification(this::configure)
+            .addOptionsModification(options -> options.enableInlining = false)
             .allowUnusedProguardConfigurationRules(),
         main,
         readProgramFiles(programFiles),
-        preservedClassNames::contains,
-        options -> {
-          this.configure(options);
-          options.enableInlining = false;
-        });
+        preservedClassNames::contains);
   }
 
   @Test
@@ -423,7 +438,7 @@
             "classmerging.MethodCollisionTest$C",
             "classmerging.MethodCollisionTest$D");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -447,7 +462,7 @@
             "classmerging.NestedDefaultInterfaceMethodsTest$B",
             "classmerging.NestedDefaultInterfaceMethodsTest$C");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -470,7 +485,7 @@
             "classmerging.NestedDefaultInterfaceMethodsTest$B",
             "classmerging.NestedDefaultInterfaceMethodsTest$C");
     runTestOnInput(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -499,7 +514,7 @@
             "classmerging.PinnedParameterTypesTest$InterfaceImpl",
             "classmerging.PinnedParameterTypesTest$TestClass");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -524,7 +539,7 @@
             "classmerging.PinnedArrayParameterTypesTest$InterfaceImpl",
             "classmerging.PinnedArrayParameterTypesTest$TestClass");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -543,65 +558,60 @@
         };
 
     // Try without vertical class merging, to check that output is as expected.
-    String expectedProguardMapWithoutClassMerging =
-        Joiner.on(System.lineSeparator())
-            .join(
-                "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
-                "    1:2:void main(java.lang.String[]):10:11 -> main",
-                "classmerging.ProguardFieldMapTest$A -> classmerging.ProguardFieldMapTest$A:",
-                "    1:1:void <init>():14:14 -> <init>",
-                "    2:2:void <init>():16:16 -> <init>",
-                "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
-                "    1:1:void <init>():20:20 -> <init>",
-                "    1:1:void test():23:23 -> test");
-    runTestOnInput(
-        testForR8(Backend.DEX)
-            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-            .allowUnusedProguardConfigurationRules(),
-        main,
-        readProgramFiles(programFiles),
-        Predicates.alwaysTrue(),
-        options -> {
-          configure(options);
-          options.enableVerticalClassMerging = false;
-          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-          options.proguardMapConsumer =
-              ToolHelper.consumeString(
-                  proguardMap ->
-                      assertThat(
-                          proguardMap, containsString(expectedProguardMapWithoutClassMerging)));
-        });
+    R8TestCompileResult compileResultWithoutClassMerging =
+        runTestOnInput(
+            testForR8(parameters.getBackend())
+                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                .addOptionsModification(this::configure)
+                .addOptionsModification(
+                    options -> {
+                      options.enableVerticalClassMerging = false;
+                      options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+                    })
+                .allowUnusedProguardConfigurationRules(),
+            main,
+            readProgramFiles(programFiles),
+            Predicates.alwaysTrue());
+
+    ClassNameMapper mappingWithoutClassMerging =
+        ClassNameMapper.mapperFromString(compileResultWithoutClassMerging.getProguardMap());
+    ClassNamingForNameMapper mappingsForClassAWithoutClassMerging =
+        mappingWithoutClassMerging.getClassNaming("classmerging.ProguardFieldMapTest$A");
+    assertTrue(mappingsForClassAWithoutClassMerging.allFieldNamings().isEmpty());
 
     // Try with vertical class merging.
-    String expectedProguardMapWithClassMerging =
-        Joiner.on(System.lineSeparator())
-            .join(
-                "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
-                "    1:2:void main(java.lang.String[]):10:11 -> main",
-                "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
-                "    java.lang.String classmerging.ProguardFieldMapTest$A.f -> f",
-                "    1:1:void classmerging.ProguardFieldMapTest$A.<init>():14:14 -> <init>",
-                "    1:1:void <init>():20 -> <init>",
-                "    2:2:void classmerging.ProguardFieldMapTest$A.<init>():16:16 -> <init>",
-                "    2:2:void <init>():20 -> <init>",
-                "    1:1:void test():23:23 -> test");
     Set<String> preservedClassNames =
         ImmutableSet.of("classmerging.ProguardFieldMapTest", "classmerging.ProguardFieldMapTest$B");
-    runTestOnInput(
-        testForR8(Backend.DEX)
-            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-            .allowUnusedProguardConfigurationRules(),
-        main,
-        readProgramFiles(programFiles),
-        preservedClassNames::contains,
-        options -> {
-          configure(options);
-          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-          options.proguardMapConsumer =
-              ToolHelper.consumeString(
-                  proguardMap ->
-                      assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging)));
-        });
+    R8TestCompileResult compileResultWithClassMerging =
+        runTestOnInput(
+            testForR8(parameters.getBackend())
+                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                .addOptionsModification(this::configure)
+                .addOptionsModification(
+                    options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+                .allowUnusedProguardConfigurationRules(),
+            main,
+            readProgramFiles(programFiles),
+            preservedClassNames::contains);
+
+    ClassNameMapper mappingWithClassMerging =
+        ClassNameMapper.mapperFromString(compileResultWithClassMerging.getProguardMap());
+    assertNull(mappingWithClassMerging.getClassNaming("classmerging.ProguardFieldMapTest$A"));
+
+    ClassNamingForNameMapper mappingsForClassBWithClassMerging =
+        mappingWithClassMerging.getClassNaming("classmerging.ProguardFieldMapTest$B");
+    assertTrue(
+        mappingsForClassBWithClassMerging.allFieldNamings().stream()
+            .anyMatch(
+                fieldNaming ->
+                    fieldNaming
+                            .getOriginalSignature()
+                            .toString()
+                            .equals("java.lang.String classmerging.ProguardFieldMapTest$A.f")
+                        && fieldNaming
+                            .getRenamedSignature()
+                            .toString()
+                            .equals("java.lang.String f")));
   }
 
   @Test
@@ -615,68 +625,68 @@
         };
 
     // Try without vertical class merging, to check that output is as expected.
-    String expectedProguardMapWithoutClassMerging =
-        Joiner.on(System.lineSeparator())
-            .join(
-                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
-                "    1:2:void main(java.lang.String[]):10:11 -> main",
-                "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
-                "    1:1:void <init>():14:14 -> <init>",
-                "    1:1:void method():17:17 -> method",
-                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
-                "    1:1:void <init>():22:22 -> <init>",
-                "    1:2:void method():26:27 -> method");
-    runTestOnInput(
-        testForR8(Backend.DEX)
-            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-            .allowUnusedProguardConfigurationRules(),
-        main,
-        readProgramFiles(programFiles),
-        Predicates.alwaysTrue(),
-        options -> {
-          configure(options);
-          options.enableVerticalClassMerging = false;
-          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-          options.proguardMapConsumer =
-              ToolHelper.consumeString(
-                  proguardMap ->
-                      assertThat(
-                          proguardMap, containsString(expectedProguardMapWithoutClassMerging)));
-        });
+    R8TestCompileResult compileResultWithoutClassMerging =
+        runTestOnInput(
+            testForR8(parameters.getBackend())
+                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                .addOptionsModification(this::configure)
+                .addOptionsModification(
+                    options -> {
+                      options.enableVerticalClassMerging = false;
+                      options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+                    })
+                .allowUnusedProguardConfigurationRules(),
+            main,
+            readProgramFiles(programFiles),
+            Predicates.alwaysTrue());
+
+    ClassNameMapper mappingWithoutClassMerging =
+        ClassNameMapper.mapperFromString(compileResultWithoutClassMerging.getProguardMap());
+    ClassNamingForNameMapper mappingsForClassAWithoutClassMerging =
+        mappingWithoutClassMerging.getClassNaming("classmerging.ProguardMethodMapTest$A");
+    assertTrue(
+        mappingsForClassAWithoutClassMerging.allMethodNamings().stream()
+            .allMatch(
+                methodNaming ->
+                    methodNaming
+                        .getOriginalSignature()
+                        .toString()
+                        .equals(methodNaming.getRenamedSignature().toString())));
 
     // Try with vertical class merging.
-    String expectedProguardMapWithClassMerging =
-        Joiner.on(System.lineSeparator())
-            .join(
-                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
-                "    1:2:void main(java.lang.String[]):10:11 -> main",
-                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
-                // TODO(christofferqa): Should this be "...<init>():14 -> <init>" to reflect that
-                // A.<init> has been inlined into B.<init>?
-                "    1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
-                // TODO(christofferqa): Should this be " ...<init>():21:21 -> <init>"?
-                "    1:1:void <init>():22 -> <init>",
-                "    1:2:void method():26:27 -> method",
-                "    1:1:void classmerging.ProguardMethodMapTest$A.method():17:17 -> "
-                    + "method$classmerging$ProguardMethodMapTest$A");
     Set<String> preservedClassNames =
         ImmutableSet.of(
             "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
-    runTestOnInput(
-        testForR8(Backend.DEX)
-            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-            .allowUnusedProguardConfigurationRules(),
-        main,
-        readProgramFiles(programFiles),
-        preservedClassNames::contains,
-        options -> {
-          configure(options);
-          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-          options.proguardMapConsumer =
-              ToolHelper.consumeString(
-                  proguardMap ->
-                      assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging)));
-        });
+    R8TestCompileResult compileResultWithClassMerging =
+        runTestOnInput(
+            testForR8(parameters.getBackend())
+                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                .addOptionsModification(this::configure)
+                .addOptionsModification(
+                    options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
+                .allowUnusedProguardConfigurationRules(),
+            main,
+            readProgramFiles(programFiles),
+            preservedClassNames::contains);
+
+    ClassNameMapper mappingWithClassMerging =
+        ClassNameMapper.mapperFromString(compileResultWithClassMerging.getProguardMap());
+    assertNull(mappingWithClassMerging.getClassNaming("classmerging.ProguardMethodMapTest$A"));
+
+    ClassNamingForNameMapper mappingsForClassBWithClassMerging =
+        mappingWithClassMerging.getClassNaming("classmerging.ProguardMethodMapTest$B");
+    assertTrue(
+        mappingsForClassBWithClassMerging.allMethodNamings().stream()
+            .anyMatch(
+                methodNaming ->
+                    methodNaming
+                            .getOriginalSignature()
+                            .toString()
+                            .equals("void classmerging.ProguardMethodMapTest$A.method()")
+                        && methodNaming
+                            .getRenamedSignature()
+                            .toString()
+                            .equals("void method$classmerging$ProguardMethodMapTest$A()")));
   }
 
   @Test
@@ -690,74 +700,80 @@
         };
 
     // Try without vertical class merging, to check that output is as expected.
-    String expectedProguardMapWithoutClassMerging =
-        Joiner.on(System.lineSeparator())
-            .join(
-                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
-                "    1:2:void main(java.lang.String[]):10:11 -> main",
-                "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
-                "    1:1:void <init>():14:14 -> <init>",
-                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
-                "    1:1:void <init>():22:22 -> <init>",
-                "    1:1:void method():26:26 -> method",
-                "    2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
-                "    2:2:void method():27 -> method");
-    runTestOnInput(
-        testForR8(Backend.DEX)
-            .addKeepRules(
-                getProguardConfig(
-                    EXAMPLE_KEEP,
-                    "-forceinline class classmerging.ProguardMethodMapTest$A { public void"
-                        + " method(); }"))
-            .allowUnusedProguardConfigurationRules(),
-        main,
-        readProgramFiles(programFiles),
-        Predicates.alwaysTrue(),
-        options -> {
-          configure(options);
-          options.enableVerticalClassMerging = false;
-          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-          options.proguardMapConsumer =
-              ToolHelper.consumeString(
-                  proguardMap ->
-                      assertThat(
-                          proguardMap, containsString(expectedProguardMapWithoutClassMerging)));
-        });
+    R8TestCompileResult compileResultWithoutClassMerging =
+        runTestOnInput(
+            testForR8(parameters.getBackend())
+                .addKeepRules(
+                    getProguardConfig(
+                        EXAMPLE_KEEP,
+                        "-forceinline class classmerging.ProguardMethodMapTest$A { public void"
+                            + " method(); }"))
+                .addOptionsModification(this::configure)
+                .addOptionsModification(
+                    options -> {
+                      options.enableVerticalClassMerging = false;
+                      options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+                    })
+                .allowUnusedProguardConfigurationRules(),
+            main,
+            readProgramFiles(programFiles),
+            Predicates.alwaysTrue());
+
+    ClassNameMapper mappingWithoutClassMerging =
+        ClassNameMapper.mapperFromString(compileResultWithoutClassMerging.getProguardMap());
+    ClassNamingForNameMapper mappingsForClassBWithoutClassMerging =
+        mappingWithoutClassMerging.getClassNaming("classmerging.ProguardMethodMapTest$B");
+    assertTrue(
+        mappingsForClassBWithoutClassMerging
+            .getMappedRangesForRenamedName("method")
+            .getMappedRanges()
+            .stream()
+            .anyMatch(
+                mappedRange ->
+                    mappedRange
+                            .signature
+                            .toString()
+                            .equals("void classmerging.ProguardMethodMapTest$A.method()")
+                        && mappedRange.renamedName.equals("method")));
 
     // Try with vertical class merging.
-    String expectedProguardMapWithClassMerging =
-        Joiner.on(System.lineSeparator())
-            .join(
-                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
-                "    1:2:void main(java.lang.String[]):10:11 -> main",
-                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
-                "    1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
-                "    1:1:void <init>():22 -> <init>",
-                "    1:1:void method():26:26 -> method",
-                "    2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
-                "    2:2:void method():27 -> method");
     Set<String> preservedClassNames =
         ImmutableSet.of(
             "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
-    runTestOnInput(
-        testForR8(Backend.DEX)
-            .addKeepRules(
-                getProguardConfig(
-                    EXAMPLE_KEEP,
-                    "-forceinline class classmerging.ProguardMethodMapTest$A { public void"
-                        + " method(); }"))
-            .allowUnusedProguardConfigurationRules(),
-        main,
-        readProgramFiles(programFiles),
-        preservedClassNames::contains,
-        options -> {
-          configure(options);
-          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-          options.proguardMapConsumer =
-              ToolHelper.consumeString(
-                  proguardMap ->
-                      assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging)));
-        });
+    R8TestCompileResult compileResultWithClassMerging =
+        runTestOnInput(
+            testForR8(parameters.getBackend())
+                .addKeepRules(
+                    getProguardConfig(
+                        EXAMPLE_KEEP,
+                        "-forceinline class classmerging.ProguardMethodMapTest$A { public void"
+                            + " method(); }"))
+                .addOptionsModification(this::configure)
+                .addOptionsModification(
+                    options -> {
+                      options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+                    })
+                .allowUnusedProguardConfigurationRules(),
+            main,
+            readProgramFiles(programFiles),
+            preservedClassNames::contains);
+
+    ClassNameMapper mappingWithClassMerging =
+        ClassNameMapper.mapperFromString(compileResultWithClassMerging.getProguardMap());
+    ClassNamingForNameMapper mappingsForClassBWithClassMerging =
+        mappingWithClassMerging.getClassNaming("classmerging.ProguardMethodMapTest$B");
+    assertTrue(
+        mappingsForClassBWithClassMerging
+            .getMappedRangesForRenamedName("method")
+            .getMappedRanges()
+            .stream()
+            .anyMatch(
+                mappedRange ->
+                    mappedRange
+                            .signature
+                            .toString()
+                            .equals("void classmerging.ProguardMethodMapTest$A.method()")
+                        && mappedRange.renamedName.equals("method")));
   }
 
   @Test
@@ -774,7 +790,7 @@
             "classmerging.SubClassThatReferencesSuperMethod",
             "classmerging.SuperCallRewritingTest");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -792,6 +808,8 @@
   // merging, R8 should not rewrite the invoke-super instruction into invoke-direct.
   @Test
   public void testSuperCallNotRewrittenToDirect() throws Throwable {
+    assumeTrue(parameters.isDexRuntime()); // Due to smali input.
+
     String main = "classmerging.SuperCallRewritingTest";
     Path[] programFiles =
         new Path[] {
@@ -834,7 +852,7 @@
         .run(main)
         .assertSuccessWithOutput(expectedOutput);
 
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .addOptionsModification(this::configure)
         .addKeepMainRule(main)
         // Keep the classes to avoid merge, but don't keep methods which allows inlining.
@@ -876,8 +894,11 @@
   //     public void invokeMethodOnF() { "invoke-super F.m()" }
   //   }
   @Test
-  @IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
   public void testSuperCallToMergedClassIsRewritten() throws Throwable {
+    assumeTrue(parameters.isDexRuntime()); // Due to smali input.
+    assumeFalse(parameters.getRuntime().asDex().getVm().getVersion() == Version.V5_1_1);
+    assumeFalse(parameters.getRuntime().asDex().getVm().getVersion() == Version.V6_0_1);
+
     String main = "classmerging.SuperCallToMergedClassIsRewrittenTest";
     Set<String> preservedClassNames =
         ImmutableSet.of(
@@ -986,7 +1007,7 @@
 
     // Run test.
     runTestOnInput(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(String.format("-keep class %s { public static void main(...); }", main)),
         main,
         appBuilder.build(),
@@ -1016,18 +1037,19 @@
               "classmerging.SyntheticBridgeSignaturesTest$ASub",
               "classmerging.SyntheticBridgeSignaturesTest$BSub");
       runTestOnInput(
-          testForR8(Backend.DEX)
+          testForR8(parameters.getBackend())
               .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+              .addOptionsModification(this::configure)
+              .addOptionsModification(
+                  options -> {
+                    if (!allowInlining) {
+                      options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+                    }
+                  })
               .allowUnusedProguardConfigurationRules(),
           main,
           readProgramFiles(programFiles),
           preservedClassNames::contains,
-          options -> {
-            this.configure(options);
-            if (!allowInlining) {
-              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-            }
-          },
           // TODO(christofferqa): The debug test fails when inlining is not allowed.
           allowInlining ? new VerticalClassMergerDebugTest(main) : null);
     }
@@ -1055,7 +1077,7 @@
             "classmerging.ConflictingInterfaceSignaturesTest",
             "classmerging.ConflictingInterfaceSignaturesTest$InterfaceImpl");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -1083,12 +1105,13 @@
             "classmerging.ExceptionTest$Exception2");
     CodeInspector inspector =
         runTest(
-            testForR8(Backend.DEX)
-                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
-                .allowUnusedProguardConfigurationRules(),
-            main,
-            programFiles,
-            preservedClassNames::contains);
+                testForR8(parameters.getBackend())
+                    .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                    .allowUnusedProguardConfigurationRules(),
+                main,
+                programFiles,
+                preservedClassNames::contains)
+            .inspector();
 
     ClassSubject mainClass = inspector.clazz(main);
     assertThat(mainClass, isPresent());
@@ -1098,14 +1121,11 @@
     assertThat(mainMethod, isPresent());
 
     // Check that the second catch handler has been removed.
-    DexCode code = mainMethod.getMethod().getCode().asDexCode();
-    int numberOfMoveExceptionInstructions = 0;
-    for (Instruction instruction : code.instructions) {
-      if (instruction instanceof MoveException) {
-        numberOfMoveExceptionInstructions++;
-      }
-    }
-    assertEquals(2, numberOfMoveExceptionInstructions);
+    assertEquals(
+        2,
+        Streams.stream(mainMethod.iterateTryCatches())
+            .flatMapToInt(x -> IntStream.of(x.getNumberOfHandlers()))
+            .sum());
   }
 
   @Test
@@ -1140,7 +1160,7 @@
         containsString("invokeinterface classmerging.MergeDefaultMethodIntoClassTest$A.f()V"));
 
     runTestOnInput(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -1164,7 +1184,7 @@
             "classmerging.ClassWithNativeMethodTest$A",
             "classmerging.ClassWithNativeMethodTest$B");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -1195,7 +1215,7 @@
             "classmerging.pkg.SimpleInterfaceImplRetriever",
             "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -1226,7 +1246,7 @@
     // Allow access modifications (and prevent SimpleInterfaceImplRetriever from being removed as
     // a result of inlining).
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(
                 getProguardConfig(
                     EXAMPLE_KEEP,
@@ -1257,7 +1277,7 @@
             "classmerging.RewritePinnedMethodTest$A",
             "classmerging.RewritePinnedMethodTest$C");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(
                 getProguardConfig(
                     EXAMPLE_KEEP, "-keep class classmerging.RewritePinnedMethodTest$A { *; }"))
@@ -1280,7 +1300,7 @@
         ImmutableSet.of(
             "classmerging.TemplateMethodTest", "classmerging.TemplateMethodTest$AbstractClassImpl");
     runTest(
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .allowUnusedProguardConfigurationRules(),
         main,
@@ -1288,7 +1308,7 @@
         preservedClassNames::contains);
   }
 
-  private CodeInspector runTest(
+  private R8TestCompileResult runTest(
       R8FullTestBuilder builder,
       String main,
       Path[] programFiles,
@@ -1297,40 +1317,27 @@
     return runTestOnInput(builder, main, readProgramFiles(programFiles), preservedClassNames);
   }
 
-  private CodeInspector runTestOnInput(
+  private R8TestCompileResult runTestOnInput(
       R8FullTestBuilder builder,
       String main,
       AndroidApp input,
       Predicate<String> preservedClassNames)
       throws Throwable {
-    return runTestOnInput(builder, main, input, preservedClassNames, this::configure);
-  }
-
-  private CodeInspector runTestOnInput(
-      R8FullTestBuilder builder,
-      String main,
-      AndroidApp input,
-      Predicate<String> preservedClassNames,
-      Consumer<InternalOptions> optionsConsumer)
-      throws Throwable {
     return runTestOnInput(
-        builder,
+        builder.addOptionsModification(this::configure),
         main,
         input,
         preservedClassNames,
-        optionsConsumer,
         new VerticalClassMergerDebugTest(main));
   }
 
-  private CodeInspector runTestOnInput(
+  private R8TestCompileResult runTestOnInput(
       R8FullTestBuilder builder,
       String main,
       AndroidApp input,
       Predicate<String> preservedClassNames,
-      Consumer<InternalOptions> optionsConsumer,
       VerticalClassMergerDebugTest debugTestRunner)
       throws Throwable {
-    Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
     R8TestCompileResult compileResult =
         builder
             .apply(
@@ -1344,13 +1351,12 @@
                 })
             .noMinification()
             .enableProguardTestOptions()
-            .addOptionsModification(
-                o -> {
-                  optionsConsumer.accept(o);
-                  o.proguardMapConsumer = new FileConsumer(proguardMapPath, o.proguardMapConsumer);
-                })
+            .setMinApi(parameters.getApiLevel())
             .compile();
 
+    Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
+    FileUtils.writeTextFile(proguardMapPath, compileResult.getProguardMap());
+
     CodeInspector inputInspector = new CodeInspector(input);
     CodeInspector outputInspector = compileResult.inspector();
 
@@ -1371,13 +1377,14 @@
             .run(main)
             .assertSuccess()
             .getStdOut();
-    compileResult.run(main).assertSuccessWithOutput(d8Result);
+
+    compileResult.run(parameters.getRuntime(), main).assertSuccessWithOutput(d8Result);
     // Check that we never come across a method that has a name with "$classmerging$" in it during
     // debugging.
-    if (debugTestRunner != null) {
+    if (debugTestRunner != null && parameters.isDexRuntime()) {
       debugTestRunner.test(compileResult.app, proguardMapPath);
     }
-    return outputInspector;
+    return compileResult;
   }
 
   private String getProguardConfig(Path path, String... additionalRules) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 75982e9..867b61c 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -68,7 +68,7 @@
     testIncompleteNestError();
   }
 
-  private TestCompileResult compileOnlyClassesMatching(
+  private TestCompileResult<?, ?> compileOnlyClassesMatching(
       Matcher<String> matcher,
       boolean d8,
       boolean allowDiagnosticWarningMessages,
@@ -142,7 +142,8 @@
 
   private void testIncompleteNestWarning(boolean d8, boolean desugarWarning) throws Exception {
     Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
-    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
+    TestCompileResult<?, ?> compileResult =
+        compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
     assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
     if (desugarWarning) {
       assertTrue(
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress148461139.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress148461139.java
new file mode 100644
index 0000000..d2d8c86
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress148461139.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class Regress148461139 extends TestBase {
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public Regress148461139(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    // This only failed if tree-shaking was disabled.
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Condition.class)
+        .noTreeShaking()
+        .compile()
+        .inspectProguardConfiguration(p -> {
+          assertNotNull(p.getKeepAllRule());
+        })
+        .inspect(i -> assertTrue(i.clazz(Condition.class).isAbstract()));
+  }
+
+  public interface Condition {
+    boolean isTrue();
+
+    static Condition runOnUiThread() {
+      return () -> true;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalStaticGetCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalStaticGetCanonicalizationTest.java
new file mode 100644
index 0000000..fbec6ad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalStaticGetCanonicalizationTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.canonicalization;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.ReprocessClassInitializer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalStaticGetCanonicalizationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public IllegalStaticGetCanonicalizationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(IllegalStaticGetCanonicalizationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.enableRedundantFieldLoadElimination = false)
+        .enableInliningAnnotations()
+        .enableReprocessClassInitializerAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello!", "Hello!");
+  }
+
+  @ReprocessClassInitializer
+  static class TestClass {
+
+    static TestClass INSTANCE;
+
+    static {
+      INSTANCE = new TestClass();
+      INSTANCE.method();
+      INSTANCE.method();
+    }
+
+    public static void main(String[] args) {}
+
+    @NeverInline
+    void method() {
+      System.out.println("Hello!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
new file mode 100644
index 0000000..4b8ceff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverReprocessMethod;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collection;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldWriteBeforeFieldReadTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldWriteBeforeFieldReadTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldWriteBeforeFieldReadTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              options.testing.waveModifier =
+                  (waves) -> {
+                    Function<String, Predicate<Collection<DexEncodedMethod>>> wavePredicate =
+                        methodName ->
+                            wave ->
+                                wave.stream()
+                                    .anyMatch(
+                                        method ->
+                                            method.method.toSourceString().contains(methodName));
+                    int readFieldsWaveIndex =
+                        IterableUtils.firstIndexMatching(waves, wavePredicate.apply("readFields"));
+                    assertTrue(readFieldsWaveIndex >= 0);
+                    int writeFieldsWaveIndex =
+                        IterableUtils.firstIndexMatching(waves, wavePredicate.apply("writeFields"));
+                    // TODO(b/144739580): Should be false.
+                    assertTrue(writeFieldsWaveIndex >= readFieldsWaveIndex);
+                  };
+            })
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNeverReprocessMethodAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+    // TODO(b/144739580): Should be absent.
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+  }
+
+  static class TestClass {
+
+    static boolean alwaysFalse;
+
+    public static void main(String[] args) {
+      A obj = new A();
+      writeFields(obj);
+      readFields(obj);
+    }
+
+    @NeverInline
+    static void writeFields(A obj) {
+      alwaysFalse = false;
+      obj.alwaysFalse = false;
+      increaseDistanceToNearestLeaf();
+    }
+
+    static void increaseDistanceToNearestLeaf() {}
+
+    @NeverInline
+    @NeverReprocessMethod
+    static void readFields(A obj) {
+      if (alwaysFalse || obj.alwaysFalse) {
+        dead();
+      } else {
+        live();
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    boolean alwaysFalse;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
new file mode 100644
index 0000000..83831d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AbstractInMiddleTest extends TestBase {
+
+  public static final String[] EXPECTED = new String[] {"A.foo", "C.foo", "C.foo", "C.foo"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AbstractInMiddleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(A.class, B.class, C.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    Set<String> targets =
+        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    // TODO(b/148591377): Should we report B.foo()?
+    ImmutableSet<String> expected =
+        ImmutableSet.of(
+            A.class.getTypeName() + ".foo",
+            B.class.getTypeName() + ".foo",
+            C.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(AbstractInMiddleTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(AbstractInMiddleTest.class)
+        .enableMergeAnnotations()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  public static class A {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  @NeverMerge
+  public abstract static class B extends A {
+
+    @Override
+    public abstract void foo();
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  public static class C extends B {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+      new C().foo();
+      ((A) new C()).foo();
+      ((B) new C()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
new file mode 100644
index 0000000..988f1a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodInSubInterfaceSubTypeTest extends TestBase {
+
+  private static final String EXPECTED = "J.foo";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultInterfaceMethodInSubInterfaceSubTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, J.class, A.class, B.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    Set<String> targets =
+        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    // TODO(b/148591377): I.foo() should ideally not be included in the set.
+    ImmutableSet<String> expected =
+        ImmutableSet.of(I.class.getTypeName() + ".foo", J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(DefaultInterfaceMethodInSubInterfaceSubTypeTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DefaultInterfaceMethodInSubInterfaceSubTypeTest.class)
+        .addKeepMainRule(Main.class)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  @NeverMerge
+  public interface J extends I {
+    @Override
+    @NeverInline
+    default void foo() {
+      System.out.println("J.foo");
+    }
+  }
+
+  @NeverMerge
+  public abstract static class A implements I {}
+
+  @NeverClassInline
+  public static class B extends A implements J {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((A) new B()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
new file mode 100644
index 0000000..89f17da
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodInSubInterfaceTest extends TestBase {
+
+  private static final String EXPECTED = "J.foo";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultInterfaceMethodInSubInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, J.class, A.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    Set<String> targets =
+        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(DefaultInterfaceMethodInSubInterfaceTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DefaultInterfaceMethodInSubInterfaceTest.class)
+        .addKeepMainRule(Main.class)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+    void foo();
+  }
+
+  @NeverMerge
+  public interface J extends I {
+    @Override
+    @NeverInline
+    default void foo() {
+      System.out.println("J.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements J {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
new file mode 100644
index 0000000..2f979d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualToInterfaceDefinitionTest extends TestBase {
+
+  private static final String EXPECTED = "I.foo";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeVirtualToInterfaceDefinitionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(buildClasses(A.class, I.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    Set<String> targets =
+        appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected = ImmutableSet.of(I.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addInnerClasses(InvokeVirtualToInterfaceDefinitionTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeVirtualToInterfaceDefinitionTest.class)
+        .addKeepMainRule(Main.class)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+
+    @NeverInline
+    default void foo() {
+      System.out.println("I.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements I {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
new file mode 100644
index 0000000..45c208a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.resolution.virtualtargets.package_a.Middle;
+import com.android.tools.r8.resolution.virtualtargets.package_a.Top;
+import com.android.tools.r8.resolution.virtualtargets.package_a.TopRunner;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateChainTest extends TestBase {
+
+  private static final String[] EXPECTED = new String[] {"Middle.clear()", "Bottom.clear()"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PackagePrivateChainTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(Top.class, Middle.class, Bottom.class, TopRunner.class, Main.class)
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailureWithErrorThatMatches(containsString("clear overrides final"));
+    } else {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    }
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    // TODO(b/148584615): Fix test.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Top.class, Middle.class, Bottom.class, TopRunner.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Bottom.clear()", "Bottom.clear()");
+  }
+
+  public static class Bottom extends Middle {
+
+    public void clear() {
+      System.out.println("Bottom.clear()");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Bottom bottom = new Bottom();
+      TopRunner.run(bottom);
+      bottom.clear();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
new file mode 100644
index 0000000..e786da6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunner;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunnerWithCast;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateFinalOverrideTest extends TestBase {
+
+  private static final String[] EXPECTED =
+      new String[] {"ViewModel.clear()", "MyViewModel.clear()", "ViewModel.clear()"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PackagePrivateFinalOverrideTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(
+                MyViewModel.class, ViewModel.class, Main.class, ViewModelRunner.class)
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailureWithErrorThatMatches(containsString("clear overrides final"));
+    } else {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    }
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(
+                MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class)
+            .addKeepMainRule(Main.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
+    } else {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+    }
+  }
+
+  @Test
+  public void testRuntimeWithInvalidInvoke()
+      throws ExecutionException, CompilationFailedException, IOException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class)
+            .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailureWithErrorThatMatches(containsString("clear overrides final"));
+    } else {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.IllegalAccessError"));
+    }
+  }
+
+  @Test
+  public void testR8WithInvalidInvoke()
+      throws ExecutionException, CompilationFailedException, IOException {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class)
+            .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear())
+            .addKeepMainRule(Main.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
+    } else {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+    }
+  }
+
+  @Test
+  public void testRuntimeWithAmbiguousInvoke()
+      throws ExecutionException, CompilationFailedException, IOException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class)
+            .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailureWithErrorThatMatches(containsString("clear overrides final"));
+    } else {
+      runResult.assertSuccessWithOutputLines(
+          "ViewModel.clear()", "MyViewModel.clear()", "MyViewModel.clear()");
+    }
+  }
+
+  @Test
+  public void testR8WithAmbiguousInvoke()
+      throws ExecutionException, CompilationFailedException, IOException {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class)
+            .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget())
+            .addKeepMainRule(Main.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      runResult.assertFailureWithErrorThatMatches(containsString("overrides final"));
+    } else {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+    }
+  }
+
+  private byte[] getModifiedMainWithIllegalInvokeToViewModelClear() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("clear")) {
+                continuation.apply(
+                    opcode,
+                    DescriptorUtils.getBinaryNameFromJavaType(ViewModel.class.getTypeName()),
+                    name,
+                    descriptor,
+                    isInterface);
+              } else {
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  private byte[] getModifiedViewModelRunnerWithDirectMyViewModelTarget() throws IOException {
+    return transformer(ViewModelRunnerWithCast.class)
+        .setClassDescriptor(
+            DescriptorUtils.javaTypeToDescriptor(ViewModelRunner.class.getTypeName()))
+        .transformMethodInsnInMethod(
+            "run",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("clearBridge")) {
+                continuation.apply(opcode, owner, "clear", descriptor, isInterface);
+              } else {
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  @NeverClassInline
+  public static class MyViewModel extends ViewModel {
+
+    @NeverInline
+    public void clear() {
+      System.out.println("MyViewModel.clear()");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      MyViewModel myViewModel = new MyViewModel();
+      myViewModel.clearBridge();
+      myViewModel.clear();
+      ViewModelRunner.run(myViewModel);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
new file mode 100644
index 0000000..5bef2ca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TargetInDefaultMethodTest extends TestBase {
+
+  private static final String EXPECTED = "I.foo";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public TargetInDefaultMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(I.class, A.class, B.class, C.class, Main.class).build(), Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+    Set<String> targets =
+        resolutionResult.lookupInterfaceTargets(appInfo.withSubtyping()).stream()
+            .map(DexEncodedMethod::qualifiedName)
+            .collect(Collectors.toSet());
+    ImmutableSet<String> expected =
+        ImmutableSet.of(B.class.getTypeName() + ".foo", I.class.getTypeName() + ".foo");
+    assertEquals(expected, targets);
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addInnerClasses(TargetInDefaultMethodTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(TargetInDefaultMethodTest.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @NeverMerge
+  public interface I {
+    @NeverInline
+    default void foo() {
+      System.out.println("I.foo");
+    }
+  }
+
+  @NeverMerge
+  public abstract static class A implements I {}
+
+  @NeverClassInline
+  public static class B extends A {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends A {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      callA(args.length == 0 ? new C() : new B());
+    }
+
+    @NeverInline
+    private static void callA(A a) {
+      a.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/Middle.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/Middle.java
new file mode 100644
index 0000000..44b019d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/Middle.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets.package_a;
+
+public class Middle extends Top {
+
+  @Override
+  final void clear() {
+    System.out.println("Middle.clear()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/Top.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/Top.java
new file mode 100644
index 0000000..5d20def
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/Top.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets.package_a;
+
+public class Top {
+
+  void clear() {
+    System.out.println("Top.clear()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/TopRunner.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/TopRunner.java
new file mode 100644
index 0000000..1f96c07
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/TopRunner.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets.package_a;
+
+public class TopRunner {
+
+  public static void run(Top top) {
+    top.clear();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
new file mode 100644
index 0000000..2c5f612
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets.package_a;
+
+public abstract class ViewModel {
+
+  final void clear() {
+    System.out.println("ViewModel.clear()");
+  }
+
+  public void clearBridge() {
+    clear();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModelRunner.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModelRunner.java
new file mode 100644
index 0000000..87cc5ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModelRunner.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets.package_a;
+
+public class ViewModelRunner {
+
+  public static void run(ViewModel vm) {
+    vm.clear();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModelRunnerWithCast.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModelRunnerWithCast.java
new file mode 100644
index 0000000..9cceffa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModelRunnerWithCast.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets.package_a;
+
+import com.android.tools.r8.resolution.virtualtargets.PackagePrivateFinalOverrideTest.MyViewModel;
+
+public class ViewModelRunnerWithCast {
+
+  public static void run(ViewModel vm) {
+    MyViewModel myViewModel = (MyViewModel) vm;
+    myViewModel.clearBridge(); // <-- will be rewritten to myViewModel.clear()
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java
index 663a070..3e47f3b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java
@@ -61,4 +61,8 @@
     return isCatching(DexItemFactory.throwableDescriptorString);
   }
 
+  @Override
+  public int getNumberOfHandlers() {
+    return tryCatch.guards.size();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
index ed46590..4a96ff1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
@@ -44,4 +44,11 @@
     return tryHandler.catchAllAddr != NO_HANDLER;
   }
 
+  @Override
+  public int getNumberOfHandlers() {
+    if (tryHandler.catchAllAddr != NO_HANDLER) {
+      return tryHandler.pairs.length + 1;
+    }
+    return tryHandler.pairs.length;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java
index 838f7a8..73e9ef1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java
@@ -7,4 +7,6 @@
   RangeSubject getRange();
   boolean isCatching(String exceptionType);
   boolean hasCatchAll();
+
+  int getNumberOfHandlers();
 }
diff --git a/tools/archive.py b/tools/archive.py
index c24babb..3eb0200 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -8,6 +8,11 @@
 import jdk
 import optparse
 import os
+try:
+  import resource
+except ImportError:
+  # Not a Unix system. Do what Gandalf tells you not to.
+  pass
 import resource
 import shutil
 import subprocess
@@ -92,11 +97,11 @@
   return GetStorageDestination('gs://', version_or_path, file_name, is_master)
 
 def GetUrl(version_or_path, file_name, is_master):
-  return GetStorageDestination('http://storage.googleapis.com/',
+  return GetStorageDestination('https://storage.googleapis.com/',
                                version_or_path, file_name, is_master)
 
 def GetMavenUrl(is_master):
-  return GetVersionDestination('http://storage.googleapis.com/', '', is_master)
+  return GetVersionDestination('https://storage.googleapis.com/', '', is_master)
 
 def SetRLimitToMax():
   (soft, hard) = resource.getrlimit(resource.RLIMIT_NOFILE)
@@ -118,9 +123,10 @@
     raise Exception(options.dry_run_output
         + ' does not exist or is not a directory')
 
-  if utils.is_bot():
+  if utils.is_bot() and not utils.IsWindows():
     SetRLimitToMax()
-  PrintResourceInfo()
+  if not utils.IsWindows():
+    PrintResourceInfo()
 
   # Create maven release which uses a build that exclude dependencies.
   create_maven_release.generate_r8_maven_zip(utils.MAVEN_ZIP)
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 295e006..7714362 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -15,7 +15,7 @@
 #
 # The first two are the raw jar file and the maven compatible zip file. The
 # third is the raw jar file placed and named so that the URL
-# http://storage.googleapis.com/r8-releases/raw can be treated as a maven
+# https://storage.googleapis.com/r8-releases/raw can be treated as a maven
 # repository to fetch the artifact com.android.tools:desugar_jdk_libs:1.0.0
 
 import archive
@@ -75,7 +75,7 @@
   else:
     utils.upload_file_to_cloud_storage(file_name, destination)
     print('File available at: %s' %
-        destination.replace('gs://', 'http://storage.googleapis.com/', 1))
+        destination.replace('gs://', 'https://storage.googleapis.com/', 1))
 
 
 def Main(argv):
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 81ccd98..11e4c8b 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -70,7 +70,7 @@
         'find-xmx-min': 256,
         'find-xmx-max': 450,
         'find-xmx-range': 16,
-        'oom-threshold': 426,
+        'oom-threshold': 380,
     },
     {
         'app': 'youtube',
diff --git a/tools/r8_release.py b/tools/r8_release.py
index f74fd57..a9e66237 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -285,7 +285,7 @@
 
 def download_file(version, file, dst):
   urllib.urlretrieve(
-      ('http://storage.googleapis.com/r8-releases/raw/%s/%s' % (version, file)),
+      ('https://storage.googleapis.com/r8-releases/raw/%s/%s' % (version, file)),
       dst)
 
 
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 8b188f5..cf26883 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -1305,7 +1305,7 @@
 
     if options.hash:
       # Download r8-<hash>.jar from
-      # http://storage.googleapis.com/r8-releases/raw/.
+      # https://storage.googleapis.com/r8-releases/raw/.
       target = 'r8-{}.jar'.format(options.hash)
       update_prebuilds_in_android.download_hash(
           temp_dir, 'com/android/tools/r8/' + options.hash, target)
@@ -1314,7 +1314,7 @@
           quiet=options.quiet)
     elif options.version:
       # Download r8-<version>.jar from
-      # http://storage.googleapis.com/r8-releases/raw/.
+      # https://storage.googleapis.com/r8-releases/raw/.
       target = 'r8-{}.jar'.format(options.version)
       update_prebuilds_in_android.download_version(
           temp_dir, 'com/android/tools/r8/' + options.version, target)
diff --git a/tools/test.py b/tools/test.py
index 9925dea..9359394 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -150,7 +150,7 @@
   u_dir = uuid.uuid4()
   destination = 'gs://%s/%s' % (BUCKET, u_dir)
   utils.upload_dir_to_cloud_storage(upload_dir, destination, is_html=True)
-  url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
+  url = 'https://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
   print 'Test results available at: %s' % url
   print '@@@STEP_LINK@Test failures@%s@@@' % url
 
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index ae1ed2c..f3a0923 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -12,7 +12,7 @@
 import utils
 import urllib
 
-BUILD_ROOT = "http://storage.googleapis.com/r8-releases/raw/"
+BUILD_ROOT = "https://storage.googleapis.com/r8-releases/raw/"
 MASTER_BUILD_ROOT = "%smaster/" % BUILD_ROOT
 
 JAR_TARGETS_MAP = {