Merge commit '89bf9b479b86813e1840fc57f8e3151e104121d2' into 1.7.15-dev
Change-Id: I537c7fbf4310e0d52ebbcd9295055d0451294d0d
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 7ce2685..988bf75 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -797,6 +797,10 @@
     return false;
   }
 
+  public boolean definesFinalizer(DexItemFactory factory) {
+    return lookupVirtualMethod(factory.objectMethods.finalize) != null;
+  }
+
   public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
     return staticFields().stream()
         .anyMatch(field -> field.getStaticValue().mayHaveSideEffects());
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index f60e877..ee50ef0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -302,6 +302,8 @@
 
   public final DexType classType = createType(classDescriptor);
   public final DexType classLoaderType = createType(classLoaderDescriptor);
+  public final DexType fieldType = createType(fieldDescriptor);
+  public final DexType methodType = createType(methodDescriptor);
   public final DexType autoCloseableType = createType(autoCloseableDescriptor);
 
   public final DexType stringBuilderType = createType(stringBuilderDescriptor);
@@ -660,6 +662,7 @@
 
     public final DexMethod desiredAssertionStatus;
     public final DexMethod forName;
+    public final DexMethod forName3;
     public final DexMethod getName;
     public final DexMethod getCanonicalName;
     public final DexMethod getSimpleName;
@@ -676,8 +679,18 @@
     private ClassMethods() {
       desiredAssertionStatus = createMethod(classDescriptor,
           desiredAssertionStatusMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
-      forName = createMethod(classDescriptor,
-          forNameMethodName, classDescriptor, new DexString[]{stringDescriptor});
+      forName =
+          createMethod(
+              classDescriptor,
+              forNameMethodName,
+              classDescriptor,
+              new DexString[] {stringDescriptor});
+      forName3 =
+          createMethod(
+              classDescriptor,
+              forNameMethodName,
+              classDescriptor,
+              new DexString[] {stringDescriptor, booleanDescriptor, classLoaderDescriptor});
       getName = createMethod(classDescriptor, getNameName, stringDescriptor, DexString.EMPTY_ARRAY);
       getCanonicalName = createMethod(
           classDescriptor, getCanonicalNameName, stringDescriptor, DexString.EMPTY_ARRAY);
@@ -692,19 +705,23 @@
               constructorDescriptor,
               new DexString[] {classArrayDescriptor});
       getField = createMethod(classDescriptor, getFieldName, fieldDescriptor,
-          new DexString[]{stringDescriptor});
+          new DexString[] {stringDescriptor});
       getDeclaredField = createMethod(classDescriptor, getDeclaredFieldName, fieldDescriptor,
-          new DexString[]{stringDescriptor});
+          new DexString[] {stringDescriptor});
       getMethod = createMethod(classDescriptor, getMethodName, methodDescriptor,
-          new DexString[]{stringDescriptor, classArrayDescriptor});
+          new DexString[] {stringDescriptor, classArrayDescriptor});
       getDeclaredMethod = createMethod(classDescriptor, getDeclaredMethodName, methodDescriptor,
-          new DexString[]{stringDescriptor, classArrayDescriptor});
+          new DexString[] {stringDescriptor, classArrayDescriptor});
       newInstance =
           createMethod(classDescriptor, newInstanceName, objectDescriptor, DexString.EMPTY_ARRAY);
       getMembers = ImmutableSet.of(getField, getDeclaredField, getMethod, getDeclaredMethod);
       getNames = ImmutableSet.of(getName, getCanonicalName, getSimpleName, getTypeName);
     }
 
+    public boolean isReflectiveClassLookup(DexMethod method) {
+      return method == forName || method == forName3;
+    }
+
     public boolean isReflectiveMemberLookup(DexMethod method) {
       return getMembers.contains(method);
     }
@@ -842,19 +859,19 @@
               intFieldUpdaterDescriptor,
               newUpdaterName,
               intFieldUpdaterDescriptor,
-              new DexString[]{classDescriptor, stringDescriptor});
+              new DexString[] {classDescriptor, stringDescriptor});
       longUpdater =
           createMethod(
               longFieldUpdaterDescriptor,
               newUpdaterName,
               longFieldUpdaterDescriptor,
-              new DexString[]{classDescriptor, stringDescriptor});
+              new DexString[] {classDescriptor, stringDescriptor});
       referenceUpdater =
           createMethod(
               referenceFieldUpdaterDescriptor,
               newUpdaterName,
               referenceFieldUpdaterDescriptor,
-              new DexString[]{classDescriptor, classDescriptor, stringDescriptor});
+              new DexString[] {classDescriptor, classDescriptor, stringDescriptor});
       updaters = ImmutableSet.of(intUpdater, longUpdater, referenceUpdater);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java b/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java
index 55dce75..86d04e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java
@@ -28,7 +28,8 @@
 
     if (alias.definition.isInvokeStatic()) {
       InvokeStatic invoke = alias.definition.asInvokeStatic();
-      if (invoke.getInvokedMethod() == definitions.dexItemFactory().classMethods.forName) {
+      if (definitions.dexItemFactory().classMethods
+          .isReflectiveClassLookup(invoke.getInvokedMethod())) {
         return getDexTypeFromClassForName(invoke, definitions);
       }
     }
@@ -38,8 +39,9 @@
 
   public static DexType getDexTypeFromClassForName(
       InvokeStatic invoke, DexDefinitionSupplier definitions) {
-    assert invoke.getInvokedMethod() == definitions.dexItemFactory().classMethods.forName;
-    if (invoke.arguments().size() == 1) {
+    assert definitions.dexItemFactory().classMethods
+        .isReflectiveClassLookup(invoke.getInvokedMethod());
+    if (invoke.arguments().size() == 1 || invoke.arguments().size() == 3) {
       Value argument = invoke.arguments().get(0);
       if (argument.isConstString()) {
         ConstString constStringInstruction = argument.getConstInstruction().asConstString();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index fc57fdc..ca84fba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -22,6 +22,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class InvokeInterface extends InvokeMethodWithReceiver {
 
@@ -111,11 +112,23 @@
       return Collections.singletonList(singleTarget);
     }
     DexMethod method = getInvokedMethod();
-    // TODO(b/141580674): we could filter out some targets based on refined receiver type.
-    return appView
-        .appInfo()
-        .resolveMethodOnInterface(method.holder, method)
-        .lookupInterfaceTargets(appView.appInfo());
+    Collection<DexEncodedMethod> targets =
+        appView
+            .appInfo()
+            .resolveMethodOnInterface(method.holder, method)
+            .lookupInterfaceTargets(appView.appInfo());
+    if (targets == null) {
+      return targets;
+    }
+    DexType staticReceiverType = getInvokedMethod().holder;
+    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView.withLiveness(), this);
+    // Leverage refined receiver type if available.
+    if (refinedReceiverType != staticReceiverType) {
+      return targets.stream()
+          .filter(m -> appView.isSubtype(m.method.holder, refinedReceiverType).isPossiblyTrue())
+          .collect(Collectors.toSet());
+    }
+    return targets;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index d7585c3..9ecc99d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -25,6 +25,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 public class InvokeVirtual extends InvokeMethodWithReceiver {
 
@@ -114,11 +115,23 @@
       return Collections.singletonList(singleTarget);
     }
     DexMethod method = getInvokedMethod();
-    // TODO(b/141580674): we could filter out some targets based on refined receiver type.
-    return appView
-        .appInfo()
-        .resolveMethodOnClass(method.holder, method)
-        .lookupVirtualTargets(appView.appInfo());
+    Collection<DexEncodedMethod> targets =
+        appView
+            .appInfo()
+            .resolveMethodOnClass(method.holder, method)
+            .lookupVirtualTargets(appView.appInfo());
+    if (targets == null) {
+      return targets;
+    }
+    DexType staticReceiverType = getInvokedMethod().holder;
+    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView.withLiveness(), this);
+    // Leverage refined receiver type if available.
+    if (refinedReceiverType != staticReceiverType) {
+      return targets.stream()
+          .filter(m -> appView.isSubtype(m.method.holder, refinedReceiverType).isPossiblyTrue())
+          .collect(Collectors.toSet());
+    }
+    return targets;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 8cbc644..fb442d1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -203,7 +203,11 @@
         usages.add(usage);
       }
     }
-    feedback.setParameterUsages(method, usages.isEmpty() ? null : new ParameterUsagesInfo(usages));
+    feedback.setParameterUsages(
+        method,
+        usages.isEmpty()
+            ? DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
+            : new ParameterUsagesInfo(usages));
   }
 
   private ParameterUsage collectParameterUsages(int i, Value value) {
@@ -372,6 +376,8 @@
 
   // This method defines trivial instance initializer as follows:
   //
+  // ** The holder class must not define a finalize method.
+  //
   // ** The initializer may call the initializer of the base class, which
   //    itself must be trivial.
   //
@@ -385,6 +391,10 @@
   // (Note that this initializer does not have to have zero arguments.)
   private TrivialInitializer computeInstanceInitializerInfo(
       IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
+    if (clazz.definesFinalizer(options.itemFactory)) {
+      // Defining a finalize method can observe the side-effect of Object.<init> GC registration.
+      return null;
+    }
     Value receiver = code.getThis();
     for (Instruction insn : code.instructions()) {
       if (insn.isReturn()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
index 7e61600..e41989a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
@@ -36,15 +36,38 @@
         parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size();
   }
 
-  ParameterUsage getParameterUsage(int parameter) {
+  ParameterUsage getParameterUsage(int index) {
     for (ParameterUsage usage : parametersUsages) {
-      if (usage.index == parameter) {
+      if (usage.index == index) {
         return usage;
       }
     }
     return null;
   }
 
+  ParameterUsagesInfo remove(int index) {
+    assert parametersUsages.size() > 0;
+    assert 0 <= index && index <= ListUtils.last(parametersUsages).index;
+    ImmutableList.Builder<ParameterUsage> builder = ImmutableList.builder();
+    for (ParameterUsage usage : parametersUsages) {
+      // Once we remove or pass the designated index, copy-n-shift the remaining usages.
+      if (index < usage.index) {
+        builder.add(ParameterUsage.copyAndShift(usage, 1));
+      } else if (index == usage.index) {
+        // Do not add the parameter usage with the matched index.
+      } else {
+        // Until we meet the `parameter` of interest, keep copying.
+        assert usage.index < index;
+        builder.add(usage);
+      }
+    }
+    ImmutableList<ParameterUsage> adjustedUsages = builder.build();
+    if (adjustedUsages.isEmpty()) {
+      return DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
+    }
+    return new ParameterUsagesInfo(adjustedUsages);
+  }
+
   public final static class ParameterUsage {
 
     public final int index;
@@ -82,6 +105,18 @@
       this.isReturned = isReturned;
     }
 
+    static ParameterUsage copyAndShift(ParameterUsage original, int shift) {
+      assert original.index >= shift;
+      return new ParameterUsage(
+          original.index - shift,
+          original.ifZeroTest,
+          original.callsReceiver,
+          original.hasFieldAssignment,
+          original.hasFieldRead,
+          original.isAssignedToField,
+          original.isReturned);
+    }
+
     public boolean notUsed() {
       return ifZeroTest == null
           && callsReceiver == null
@@ -104,7 +139,7 @@
     private boolean isAssignedToField = false;
     private boolean isReturned = false;
 
-    public ParameterUsageBuilder(Value arg, int index) {
+    ParameterUsageBuilder(Value arg, int index) {
       this.arg = arg;
       this.index = index;
     }
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 09a8d13..1516f88 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
@@ -4,9 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import static com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
-import static com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
-
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
@@ -40,8 +37,9 @@
   private boolean returnsConstantString = DefaultMethodOptimizationInfo.UNKNOWN_RETURNS_CONSTANT;
   private DexString returnedConstantString =
       DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_CONSTANT_STRING;
-  private TypeLatticeElement returnsObjectOfType = UNKNOWN_TYPE;
-  private ClassTypeLatticeElement returnsObjectWithLowerBoundType = UNKNOWN_CLASS_TYPE;
+  private TypeLatticeElement returnsObjectOfType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
+  private ClassTypeLatticeElement returnsObjectWithLowerBoundType =
+      DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
   private InlinePreference inlining = InlinePreference.Default;
   private boolean useIdentifierNameString =
       DefaultMethodOptimizationInfo.DOES_NOT_USE_IDNETIFIER_NAME_STRING;
@@ -84,9 +82,16 @@
     // Intentionally left empty, just use the default values.
   }
 
+  // Copy constructor used to create a mutable copy. Do not forget to copy from template when a new
+  // field is added.
   private UpdatableMethodOptimizationInfo(UpdatableMethodOptimizationInfo template) {
     cannotBeKept = template.cannotBeKept;
+    classInitializerMayBePostponed = template.classInitializerMayBePostponed;
+    hasBeenInlinedIntoSingleCallSite = template.hasBeenInlinedIntoSingleCallSite;
+    initializedClassesOnNormalExit = template.initializedClassesOnNormalExit;
     returnedArgument = template.returnedArgument;
+    mayHaveSideEffects = template.mayHaveSideEffects;
+    returnValueOnlyDependsOnArguments = template.returnValueOnlyDependsOnArguments;
     neverReturnsNull = template.neverReturnsNull;
     neverReturnsNormally = template.neverReturnsNormally;
     returnsConstantNumber = template.returnsConstantNumber;
@@ -106,6 +111,7 @@
     nonNullParamOrThrow = template.nonNullParamOrThrow;
     nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
     reachabilitySensitive = template.reachabilitySensitive;
+    returnValueHasBeenPropagated = template.returnValueHasBeenPropagated;
   }
 
   public void fixupClassTypeReferences(
@@ -375,7 +381,7 @@
     // Nullability could be less precise, though. For example, suppose a value is known to be
     // non-null after a safe invocation, hence recorded with the non-null variant. If that call is
     // inlined and the method is reprocessed, such non-null assumption cannot be made again.
-    assert returnsObjectOfType == UNKNOWN_TYPE
+    assert returnsObjectOfType == DefaultMethodOptimizationInfo.UNKNOWN_TYPE
             || type.lessThanOrEqualUpToNullability(returnsObjectOfType, appView)
         : "return type changed from " + returnsObjectOfType + " to " + type;
     returnsObjectOfType = type;
@@ -385,7 +391,7 @@
     assert type != null;
     // Currently, we only have a lower bound type when we have _exact_ runtime type information.
     // Thus, the type should never become more precise (although the nullability could).
-    assert returnsObjectWithLowerBoundType == UNKNOWN_CLASS_TYPE
+    assert returnsObjectWithLowerBoundType == DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE
             || (type.equalUpToNullability(returnsObjectWithLowerBoundType)
                 && type.nullability()
                     .lessThanOrEqual(returnsObjectWithLowerBoundType.nullability()))
@@ -452,8 +458,12 @@
     // initializedClassesOnNormalExit: `this` could trigger <clinit> of the previous holder.
     initializedClassesOnNormalExit =
         DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
-    // TODO(b/142401154): adjustable
-    returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT;
+    // At least, `this` pointer is not used in `returnedArgument`.
+    assert returnedArgument == DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT
+        || returnedArgument > 0;
+    returnedArgument =
+        returnedArgument == DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT
+            ? DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT : returnedArgument - 1;
     // mayHaveSideEffects: `this` Argument didn't have side effects, so removing it doesn't affect
     //   whether or not the method may have side effects.
     // returnValueOnlyDependsOnArguments:
@@ -483,11 +493,19 @@
     // initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
     initializerEnablingJavaAssertions =
         DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
-    // TODO(b/142401154): adjustable
-    parametersUsages = DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
-    nonNullParamOrThrow = DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS;
+    parametersUsages =
+        parametersUsages == DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
+            ? DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
+            : parametersUsages.remove(0);
+    nonNullParamOrThrow =
+        nonNullParamOrThrow == DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
+            ? DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
+            : nonNullParamOrThrow.get(1, nonNullParamOrThrow.length());
     nonNullParamOnNormalExits =
-        DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
+        nonNullParamOnNormalExits
+                == DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS
+            ? DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS
+            : nonNullParamOnNormalExits.get(1, nonNullParamOnNormalExits.length());
     // reachabilitySensitive: doesn't depend on `this`
     // returnValueHasBeenPropagated: doesn't depend on `this`
   }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 725c68c..75d1037 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -341,7 +341,8 @@
 
     boolean isClassForName = returnType == appView.dexItemFactory().classType;
     if (isClassForName) {
-      assert invoke.getInvokedMethod() == appView.dexItemFactory().classMethods.forName;
+      assert appView.dexItemFactory().classMethods
+          .isReflectiveClassLookup(invoke.getInvokedMethod());
       return 0;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 8cfd094..da38ed4 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -42,8 +42,20 @@
    * @return {@code true} if the given {@param method} is a reflection method in Java.
    */
   public static boolean isReflectionMethod(DexItemFactory dexItemFactory, DexMethod method) {
+    // So, why is this simply not like:
+    //   return dexItemFactory.classMethods.isReflectiveClassLookup(method)
+    //       || dexItemFactory.classMethods.isReflectiveMemberLookup(method)
+    //       || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method);
+    // ?
+    // That is because the counter part of other shrinkers supports users' own reflective methods
+    // whose signature matches with reflection methods in Java. Hence, explicit signature matching.
+    // See tests {@link IdentifierMinifierTest#test2_rule3},
+    //   {@link IdentifierNameStringMarkerTest#reflective_field_singleUseOperand_renamed}, or
+    //   {@link IdentifierNameStringMarkerTest#reflective_method_singleUseOperand_renamed}.
+    //
     // For java.lang.Class:
     //   (String) -> java.lang.Class | java.lang.reflect.Field
+    //   (String, boolean, ClassLoader) -> java.lang.Class
     //   (String, Class[]) -> java.lang.reflect.Method
     // For java.util.concurrent.atomic.Atomic(Integer|Long)FieldUpdater:
     //   (Class, String) -> $holderType
@@ -53,43 +65,53 @@
     //   (Class, String) -> java.lang.reflect.Field
     //   (Class, String, Class[]) -> java.lang.reflect.Method
     int arity = method.getArity();
-    if (method.holder.descriptor == dexItemFactory.classDescriptor) {
+    if (method.holder == dexItemFactory.classType) {
       // Virtual methods of java.lang.Class, such as getField, getMethod, etc.
-      if (arity != 1 && arity != 2) {
+      if (arity == 0 || arity > 3) {
         return false;
       }
       if (arity == 1) {
-        if (method.proto.returnType.descriptor != dexItemFactory.classDescriptor
-            && method.proto.returnType.descriptor != dexItemFactory.fieldDescriptor) {
+        if (method.proto.returnType != dexItemFactory.classType
+            && method.proto.returnType != dexItemFactory.fieldType) {
+          return false;
+        }
+      } else if (arity == 2) {
+        if (method.proto.returnType != dexItemFactory.methodType) {
           return false;
         }
       } else {
-        if (method.proto.returnType.descriptor != dexItemFactory.methodDescriptor) {
+        if (method.proto.returnType != dexItemFactory.classType) {
           return false;
         }
       }
-      if (method.proto.parameters.values[0].descriptor != dexItemFactory.stringDescriptor) {
+      if (method.proto.parameters.values[0] != dexItemFactory.stringType) {
         return false;
       }
       if (arity == 2) {
-        if (method.proto.parameters.values[1].descriptor != dexItemFactory.classArrayDescriptor) {
+        if (method.proto.parameters.values[1] != dexItemFactory.classArrayType) {
+          return false;
+        }
+      }
+      if (arity == 3) {
+        if (method.proto.parameters.values[1] != dexItemFactory.booleanType
+            && method.proto.parameters.values[2] != dexItemFactory.classLoaderType) {
           return false;
         }
       }
     } else if (
         method.holder.descriptor == dexItemFactory.intFieldUpdaterDescriptor
-        || method.holder.descriptor == dexItemFactory.longFieldUpdaterDescriptor) {
+            || method.holder.descriptor == dexItemFactory.longFieldUpdaterDescriptor) {
       // Atomic(Integer|Long)FieldUpdater->newUpdater(Class, String)AtomicFieldUpdater
       if (arity != 2) {
         return false;
       }
-      if (method.proto.returnType.descriptor != method.holder.descriptor) {
+      if (method.proto.returnType != method.holder) {
         return false;
       }
-      if (method.proto.parameters.values[0].descriptor != dexItemFactory.classDescriptor) {
+      if (method.proto.parameters.values[0] != dexItemFactory.classType) {
         return false;
       }
-      if (method.proto.parameters.values[1].descriptor != dexItemFactory.stringDescriptor) {
+      if (method.proto.parameters.values[1] != dexItemFactory.stringType) {
         return false;
       }
     } else if (method.holder.descriptor == dexItemFactory.referenceFieldUpdaterDescriptor) {
@@ -97,16 +119,16 @@
       if (arity != 3) {
         return false;
       }
-      if (method.proto.returnType.descriptor != method.holder.descriptor) {
+      if (method.proto.returnType != method.holder) {
         return false;
       }
-      if (method.proto.parameters.values[0].descriptor != dexItemFactory.classDescriptor) {
+      if (method.proto.parameters.values[0] != dexItemFactory.classType) {
         return false;
       }
-      if (method.proto.parameters.values[1].descriptor != dexItemFactory.classDescriptor) {
+      if (method.proto.parameters.values[1] != dexItemFactory.classType) {
         return false;
       }
-      if (method.proto.parameters.values[2].descriptor != dexItemFactory.stringDescriptor) {
+      if (method.proto.parameters.values[2] != dexItemFactory.stringType) {
         return false;
       }
     } else {
@@ -115,22 +137,22 @@
         return false;
       }
       if (arity == 2) {
-        if (method.proto.returnType.descriptor != dexItemFactory.fieldDescriptor) {
+        if (method.proto.returnType != dexItemFactory.fieldType) {
           return false;
         }
       } else {
-        if (method.proto.returnType.descriptor != dexItemFactory.methodDescriptor) {
+        if (method.proto.returnType != dexItemFactory.methodType) {
           return false;
         }
       }
-      if (method.proto.parameters.values[0].descriptor != dexItemFactory.classDescriptor) {
+      if (method.proto.parameters.values[0] != dexItemFactory.classType) {
         return false;
       }
-      if (method.proto.parameters.values[1].descriptor != dexItemFactory.stringDescriptor) {
+      if (method.proto.parameters.values[1] != dexItemFactory.stringType) {
         return false;
       }
       if (arity == 3) {
-        if (method.proto.parameters.values[2].descriptor != dexItemFactory.classArrayDescriptor) {
+        if (method.proto.parameters.values[2] != dexItemFactory.classArrayType) {
           return false;
         }
       }
@@ -143,12 +165,12 @@
    * java.lang.String)`, and one of the arguments is defined by an invoke-instruction that calls
    * `java.lang.String java.lang.Class.getName()`.
    */
-  public static boolean isClassNameComparison(InvokeMethod invoke, DexItemFactory dexItemFactory) {
+  static boolean isClassNameComparison(InvokeMethod invoke, DexItemFactory dexItemFactory) {
     return invoke.isInvokeVirtual()
         && isClassNameComparison(invoke.asInvokeVirtual(), dexItemFactory);
   }
 
-  public static boolean isClassNameComparison(InvokeVirtual invoke, DexItemFactory dexItemFactory) {
+  static boolean isClassNameComparison(InvokeVirtual invoke, DexItemFactory dexItemFactory) {
     return invoke.getInvokedMethod() == dexItemFactory.stringMethods.equals
         && (isClassNameValue(invoke.getReceiver(), dexItemFactory)
             || isClassNameValue(invoke.inValues().get(1), dexItemFactory));
@@ -174,10 +196,12 @@
   public static DexReference identifyIdentifier(
       InvokeMethod invoke, DexDefinitionSupplier definitions) {
     List<Value> ins = invoke.arguments();
-    // The only static call: Class#forName, which receives (String) as ins.
+    // The only static calls: Class#forName,
+    //   which receive either (String) or (String, boolean, ClassLoader) as ins.
     if (invoke.isInvokeStatic()) {
       InvokeStatic invokeStatic = invoke.asInvokeStatic();
-      if (invokeStatic.getInvokedMethod() == definitions.dexItemFactory().classMethods.forName) {
+      if (definitions.dexItemFactory().classMethods
+          .isReflectiveClassLookup(invokeStatic.getInvokedMethod())) {
         return ConstantValueUtils.getDexTypeFromClassForName(invokeStatic, definitions);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 3d4d186..d5517f7 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -439,7 +440,20 @@
         DexMethod method,
         InternalNamingState internalState,
         BiPredicate<DexString, DexMethod> isAvailable) {
-      return nextName(method, method.name, method.holder, internalState, isAvailable, super::next);
+      DexEncodedMethod definition = appView.definitionFor(method);
+      DexString nextName =
+          nextName(
+              method,
+              definition,
+              method.name,
+              method.holder,
+              internalState,
+              isAvailable,
+              super::next);
+      assert nextName == method.name || !definition.isClassInitializer();
+      assert nextName == method.name
+          || !appView.definitionFor(method.holder).accessFlags.isAnnotation();
+      return nextName;
     }
 
     @Override
@@ -447,22 +461,32 @@
         DexField field,
         InternalNamingState internalState,
         BiPredicate<DexString, DexField> isAvailable) {
-      return nextName(field, field.name, field.holder, internalState, isAvailable, super::next);
+      return nextName(
+          field,
+          appView.definitionFor(field),
+          field.name,
+          field.holder,
+          internalState,
+          isAvailable,
+          super::next);
     }
 
     private <T extends DexReference> DexString nextName(
         T reference,
+        DexDefinition definition,
         DexString name,
         DexType holderType,
         InternalNamingState internalState,
         BiPredicate<DexString, T> isAvailable,
         TriFunction<T, InternalNamingState, BiPredicate<DexString, T>, DexString> generateName) {
+      assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
+      assert definition.toReference() == reference;
       DexClass holder = appView.definitionFor(holderType);
       assert holder != null;
-      DexString reservedName = getReservedName(reference, name, holder);
+      DexString reservedName = getReservedName(definition, name, holder);
       if (reservedName != null) {
         if (!isAvailable.test(reservedName, reference)) {
-          reportReservationError(reference, reservedName);
+          reportReservationError(definition.asDexReference(), reservedName);
         }
         return reservedName;
       }
@@ -473,26 +497,36 @@
 
     @Override
     public DexString getReservedName(DexEncodedMethod method, DexClass holder) {
-      return getReservedName(method.method, method.method.name, holder);
+      return getReservedName(method, method.method.name, holder);
     }
 
     @Override
     public DexString getReservedName(DexEncodedField field, DexClass holder) {
-      return getReservedName(field.field, field.field.name, holder);
+      return getReservedName(field, field.field.name, holder);
     }
 
-    private DexString getReservedName(DexReference reference, DexString name, DexClass holder) {
+    private DexString getReservedName(DexDefinition definition, DexString name, DexClass holder) {
+      assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
       // Always consult the mapping for renamed members that are not on program path.
-      if (holder.isNotProgramClass() && mappedNames.containsKey(reference)) {
-        return factory.createString(mappedNames.get(reference).getRenamedName());
-      }
-      if (holder.isProgramClass() && appView.rootSet().mayBeMinified(reference, appView)) {
+      DexReference reference = definition.toReference();
+      if (holder.isNotProgramClass()) {
         if (mappedNames.containsKey(reference)) {
           return factory.createString(mappedNames.get(reference).getRenamedName());
         }
-        return null;
+        return name;
       }
-      return name;
+      assert holder.isProgramClass();
+      DexString reservedName =
+          definition.isDexEncodedMethod()
+              ? super.getReservedName(definition.asDexEncodedMethod(), holder)
+              : super.getReservedName(definition.asDexEncodedField(), holder);
+      if (reservedName != null) {
+        return reservedName;
+      }
+      if (mappedNames.containsKey(reference)) {
+        return factory.createString(mappedNames.get(reference).getRenamedName());
+      }
+      return null;
     }
 
     @Override
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 5eebf8b..246dfa1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -434,6 +434,7 @@
           DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
           if (forceProguardCompatibility) {
             workList.enqueueMarkMethodKeptAction(
+                clazz,
                 defaultInitializer,
                 graphReporter.reportCompatKeepDefaultInitializer(clazz, defaultInitializer));
           }
@@ -443,13 +444,23 @@
         }
       }
     } else if (item.isDexEncodedField()) {
-      KeepReasonWitness witness =
-          graphReporter.reportKeepField(precondition, rules, item.asDexEncodedField());
-      workList.enqueueMarkFieldKeptAction(item.asDexEncodedField(), witness);
+      DexEncodedField dexEncodedField = item.asDexEncodedField();
+      DexProgramClass holder = getProgramClassOrNull(dexEncodedField.field.holder);
+      if (holder != null) {
+        workList.enqueueMarkFieldKeptAction(
+            holder,
+            dexEncodedField,
+            graphReporter.reportKeepField(precondition, rules, dexEncodedField));
+      }
     } else if (item.isDexEncodedMethod()) {
-      KeepReasonWitness witness =
-          graphReporter.reportKeepMethod(precondition, rules, item.asDexEncodedMethod());
-      workList.enqueueMarkMethodKeptAction(item.asDexEncodedMethod(), witness);
+      DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
+      DexProgramClass holder = getProgramClassOrNull(encodedMethod.method.holder);
+      if (holder != null) {
+        workList.enqueueMarkMethodKeptAction(
+            holder,
+            encodedMethod,
+            graphReporter.reportKeepMethod(precondition, rules, encodedMethod));
+      }
     } else {
       throw new IllegalArgumentException(item.toString());
     }
@@ -627,7 +638,7 @@
 
     boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
-      if (method == dexItemFactory.classMethods.forName
+      if (dexItemFactory.classMethods.isReflectiveClassLookup(method)
           || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
         // Implicitly add -identifiernamestring rule for the Java reflection in use.
         identifierNameStrings.add(method);
@@ -2077,7 +2088,7 @@
     if (valuesMethod != null) {
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
-      workList.enqueueMarkMethodKeptAction(valuesMethod, reason);
+      workList.enqueueMarkMethodKeptAction(clazz, valuesMethod, reason);
       pinnedItems.add(valuesMethod.toReference());
       rootSet.shouldNotBeMinified(valuesMethod.toReference());
     }
@@ -2371,12 +2382,8 @@
   }
 
   // Package protected due to entry point from worklist.
-  void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
+  void markMethodAsKept(DexProgramClass holder, DexEncodedMethod target, KeepReason reason) {
     DexMethod method = target.method;
-    DexProgramClass holder = getProgramClassOrNull(method.holder);
-    if (holder == null) {
-      return;
-    }
     if (target.isVirtualMethod()) {
       // A virtual method. Mark it as reachable so that subclasses, if instantiated, keep
       // their overrides. However, we don't mark it live, as a keep rule might not imply that
@@ -2411,11 +2418,8 @@
   }
 
   // Package protected due to entry point from worklist.
-  void markFieldAsKept(DexEncodedField target, KeepReason reason) {
-    DexProgramClass clazz = getProgramClassOrNull(target.field.holder);
-    if (clazz == null) {
-      return;
-    }
+  void markFieldAsKept(DexProgramClass holder, DexEncodedField target, KeepReason reason) {
+    assert holder.type == target.field.holder;
     if (target.accessFlags.isStatic()) {
       markStaticFieldAsLive(target, reason);
     } else {
@@ -2624,7 +2628,7 @@
       if (keepClass) {
         markInstantiated(clazz, null, KeepReason.reflectiveUseIn(method));
       }
-      markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
+      markFieldAsKept(clazz, encodedField, KeepReason.reflectiveUseIn(method));
       // Fields accessed by reflection is marked as both read and written.
       registerFieldRead(encodedField.field, method);
       registerFieldWrite(encodedField.field, method);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 275e5a7..24f72ab 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import java.util.ArrayDeque;
 import java.util.Queue;
 
@@ -127,32 +128,38 @@
   }
 
   static class MarkMethodKeptAction extends Action {
+    final DexProgramClass holder;
     final DexEncodedMethod target;
     final KeepReason reason;
 
-    public MarkMethodKeptAction(DexEncodedMethod target, KeepReason reason) {
+    public MarkMethodKeptAction(
+        DexProgramClass holder, DexEncodedMethod target, KeepReason reason) {
+      this.holder = holder;
       this.target = target;
       this.reason = reason;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markMethodAsKept(target, reason);
+      enqueuer.markMethodAsKept(holder, target, reason);
     }
   }
 
   static class MarkFieldKeptAction extends Action {
+    final DexProgramClass holder;
     final DexEncodedField target;
-    final KeepReason reason;
+    final KeepReasonWitness witness;
 
-    public MarkFieldKeptAction(DexEncodedField target, KeepReason reason) {
+    public MarkFieldKeptAction(
+        DexProgramClass holder, DexEncodedField target, KeepReasonWitness witness) {
+      this.holder = holder;
       this.target = target;
-      this.reason = reason;
+      this.witness = witness;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markFieldAsKept(target, reason);
+      enqueuer.markFieldAsKept(holder, target, witness);
     }
   }
 
@@ -211,13 +218,15 @@
     queue.add(new MarkMethodLiveAction(method, reason));
   }
 
-  void enqueueMarkMethodKeptAction(DexEncodedMethod method, KeepReason reason) {
-    assert method.isProgramMethod(appView);
-    queue.add(new MarkMethodKeptAction(method, reason));
+  void enqueueMarkMethodKeptAction(
+      DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
+    assert method.method.holder == clazz.type;
+    queue.add(new MarkMethodKeptAction(clazz, method, reason));
   }
 
-  void enqueueMarkFieldKeptAction(DexEncodedField field, KeepReason reason) {
+  void enqueueMarkFieldKeptAction(
+      DexProgramClass holder, DexEncodedField field, KeepReasonWitness witness) {
     assert field.isProgramField(appView);
-    queue.add(new MarkFieldKeptAction(field, reason));
+    queue.add(new MarkFieldKeptAction(holder, field, witness));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 79f4f6c..f570956 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -17,7 +17,6 @@
   }
 
   public static <T> T last(List<T> list) {
-    assert list instanceof ArrayList;
     return list.get(list.size() - 1);
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java
new file mode 100644
index 0000000..3285930
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2019, 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.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+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.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableSet;
+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)
+// This is a reproduction of b/142909854 where we vertically merge a class with a constructor that
+// references a class outside the main-dex collection. We did not inline those, even when
+// force-inlining, so the renamed constructor broke the init chain.
+public class VerticalClassMergerInitTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  public VerticalClassMergerInitTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMergingClassWithConstructorNotInMainDex()
+      throws IOException, CompilationFailedException, ExecutionException {
+    // TODO(b/142909854): Fix test expectation when bug has been resolved.
+    testForR8(parameters.getBackend())
+        .addInnerClasses(VerticalClassMergerInitTest.class)
+        .addKeepMainRule(Main.class)
+        .addMainDexRules("-keep class " + Main.class.getTypeName())
+        .setMinApi(AndroidApiLevel.K_WATCH)
+        .addOptionsModification(
+            options -> {
+              options.forceProguardCompatibility = true;
+              // The initializer is small in this example so only allow FORCE inlining.
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+            })
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(Base.class), not(isPresent()));
+              assertThat(inspector.clazz(Child.class), isPresent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("initialized"));
+  }
+
+  public static class Base {
+
+    public Base() {
+      System.out.println("Base.init()");
+      new Outside();
+    }
+  }
+
+  private static class Outside {
+    Outside() {
+      System.out.println("Outside.init()");
+    }
+  }
+
+  public static class Child extends Base {
+
+    // We need a static member to force the main-dex tracing to include Child and Base.
+    public static Object foo() {
+      return null;
+    }
+
+    public Child() {
+      System.out.println("Child.init()");
+    }
+  }
+
+  public static class Main {
+
+    {
+      if (Child.foo() == null) {
+        System.out.println("Main.clinit()");
+      }
+    }
+
+    public static void main(String[] args) {
+      new Child();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/AnonymousObjectWithFinalizeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/AnonymousObjectWithFinalizeTest.java
new file mode 100644
index 0000000..7337693
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/AnonymousObjectWithFinalizeTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.analysis.sideeffect;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// See b/143189461
+@RunWith(Parameterized.class)
+public class AnonymousObjectWithFinalizeTest extends TestBase {
+
+  final static String EXPECTED = StringUtils.lines(
+      "set up fence",
+      "run gc",
+      "count down fence",
+      "passed fence"
+  );
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      Runtime.getRuntime().gc();
+      Runtime.getRuntime().runFinalization();
+
+      System.out.println("set up fence");
+      final CountDownLatch fence = new CountDownLatch(1);
+      new Object() {
+        @Override
+        protected void finalize() throws Throwable {
+          try {
+            System.out.println("count down fence");
+            fence.countDown();
+          } finally {
+            super.finalize();
+          }
+        }
+      };
+      try {
+        System.out.println("run gc");
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        if (fence.await(10, TimeUnit.SECONDS)) {
+          System.out.println("passed fence");
+        } else {
+          System.out.println("fence await timed out");
+        }
+      } catch (InterruptedException ex) {
+        throw new RuntimeException(ex);
+      }
+    }
+  }
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  final TestParameters parameters;
+
+  public AnonymousObjectWithFinalizeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClassesAndInnerClasses(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addProgramClassesAndInnerClasses(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectNegativeTest.java
index 7b8b8aa..d2687fd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectNegativeTest.java
@@ -26,7 +26,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -42,7 +42,7 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1", "Sub2")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
index 1890534..0145b82 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfaceNegativeTest.java
index 4db0012..3508b62 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfaceNegativeTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -48,7 +48,7 @@
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
         })
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1", "Sub2")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
index c720d09..8cca30a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -48,7 +48,7 @@
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
         })
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A:Sub1", "B:Sub2")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticNegativeTest.java
index 1579439..fb463a0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticNegativeTest.java
@@ -25,7 +25,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -40,7 +40,7 @@
         .addInnerClasses(InvokeStaticNegativeTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1", "Sub2")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
index 98012dc..47e8c1c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
@@ -26,7 +26,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -42,7 +42,7 @@
         .addKeepMainRule(MAIN)
         .enableMergeAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualNegativeTest.java
index b32e117..9da6912 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualNegativeTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A:Sub1", "A:Sub2", "B:Sub1", "B:Sub2")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
index cd4852d..cc0f93d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A:Sub1", "B:Sub1")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
index 859365b..d84cf8e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
@@ -26,7 +26,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -42,7 +42,7 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
index 1db3da7..ef0c90b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
@@ -26,7 +26,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -42,7 +42,7 @@
         .addKeepMainRule(MAIN)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
index 9d4e509..7481286 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -48,7 +48,7 @@
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
         })
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "A")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
index b838d12..613db78 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
@@ -26,7 +26,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -46,7 +46,7 @@
           // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
           o.enableDevirtualization = false;
         })
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
new file mode 100644
index 0000000..601b19b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2019, 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.callsites.nullability;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+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.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceWithRefinedReceiverTest extends TestBase {
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeInterfaceWithRefinedReceiverTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeInterfaceWithRefinedReceiverTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
+          o.enableDevirtualization = false;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "C")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject i = inspector.clazz(I.class);
+    assertThat(i, isPresent());
+
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // Can optimize branches since `arg` is definitely null.
+    assertTrue(b_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+    ClassSubject bSub = inspector.clazz(BSub.class);
+    assertThat(bSub, isPresent());
+
+    MethodSubject bSub_m = bSub.uniqueMethodWithName("m");
+    assertThat(bSub_m, isPresent());
+    // Can optimize branches since `arg` is definitely null.
+    assertTrue(bSub_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+    ClassSubject c = inspector.clazz(C.class);
+    assertThat(c, isPresent());
+
+    MethodSubject c_m = c.uniqueMethodWithName("m");
+    assertThat(c_m, isPresent());
+    // Can optimize branches since `arg` is definitely not null.
+    assertTrue(c_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+    ClassSubject cSub = inspector.clazz(CSub.class);
+    assertThat(cSub, not(isPresent()));
+  }
+
+  interface I {
+    void m(Object arg);
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class B implements I {
+    @NeverInline
+    @Override
+    public void m(Object arg) {
+      // Technically same as String#valueOf.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "B";
+    }
+  }
+
+  @NeverClassInline
+  static class BSub extends B {
+    @NeverInline
+    @Override
+    public void m(Object arg) {
+      // Same as B#m.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "BSub";
+    }
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class C implements I {
+    @NeverInline
+    @Override
+    public void m(Object arg) {
+      // Technically same as String#valueOf.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  static class CSub extends C {
+    @NeverInline
+    @Override
+    public void m(Object arg) {
+      // Same as C#m.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "CSub";
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      I i = System.currentTimeMillis() > 0 ? new B() : new BSub();
+      i.m(null);  // No single target, but should be able to filter out C(Sub)#m
+
+      I c = new C();  // with the exact type:
+      c.m(c);         // calls C.m() with non-null instance.
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
index 5d10e42..f796efe 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
@@ -25,7 +25,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -40,7 +40,7 @@
         .addInnerClasses(InvokeStaticNegativeTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
index f1081fc..401855c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
@@ -25,7 +25,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -40,7 +40,7 @@
         .addInnerClasses(InvokeStaticPositiveTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
index d313c03..c9b215a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A", "A", "B", "B")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index 45976a0..93588d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "A", "null", "B")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index 70aa862..f8ad42c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .enableMergeAnnotations()
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("A", "null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
new file mode 100644
index 0000000..a9a62d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
@@ -0,0 +1,181 @@
+// Copyright (c) 2019, 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.callsites.nullability;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+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.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualWithRefinedReceiverTest extends TestBase {
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeVirtualWithRefinedReceiverTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeVirtualWithRefinedReceiverTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "C")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // Can optimize branches since `arg` is definitely null.
+    assertTrue(b_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+    ClassSubject bSub = inspector.clazz(BSub.class);
+    assertThat(bSub, isPresent());
+
+    MethodSubject bSub_m = bSub.uniqueMethodWithName("m");
+    assertThat(bSub_m, isPresent());
+    // Can optimize branches since `arg` is definitely null.
+    assertTrue(bSub_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+    ClassSubject c = inspector.clazz(C.class);
+    assertThat(c, isPresent());
+
+    MethodSubject c_m = c.uniqueMethodWithName("m");
+    assertThat(c_m, isPresent());
+    // Can optimize branches since `arg` is definitely not null.
+    assertTrue(c_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+    ClassSubject cSub = inspector.clazz(CSub.class);
+    assertThat(cSub, not(isPresent()));
+  }
+
+  abstract static class A {
+    abstract void m(Object arg);
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class B extends A {
+    @NeverInline
+    @Override
+    void m(Object arg) {
+      // Technically same as String#valueOf.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "B";
+    }
+  }
+
+  @NeverClassInline
+  static class BSub extends B {
+    @NeverInline
+    @Override
+    void m(Object arg) {
+      // Same as B#m.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "BSub";
+    }
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class C extends A {
+    @NeverInline
+    @Override
+    void m(Object arg) {
+      // Technically same as String#valueOf.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  static class CSub extends C {
+    @NeverInline
+    @Override
+    void m(Object arg) {
+      // Same as C#m.
+      if (arg != null) {
+        System.out.println(arg.toString());
+      } else {
+        System.out.println("null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "CSub";
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      A a = System.currentTimeMillis() > 0 ? new B() : new BSub();
+      a.m(null);  // No single target, but should be able to filter out C(Sub)#m
+
+      A c = new C();  // with the exact type:
+      c.m(c);         // calls C.m() with non-null instance.
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
index d77c180..d5b13af 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
@@ -27,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .addKeepClassAndMembersRules(A.class)
         .enableClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null", "non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
index d55484d..e600ebc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/WithStaticizerTest.java
@@ -28,7 +28,7 @@
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     // TODO(b/112831361): support for class staticizer in CF backend.
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -44,7 +44,7 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .enableClassInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Input")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/naming/ForNameRenamingTest.java b/src/test/java/com/android/tools/r8/naming/ForNameRenamingTest.java
new file mode 100644
index 0000000..1d776b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ForNameRenamingTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2019, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+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 com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ForNameRenamingTest extends TestBase {
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public ForNameRenamingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ForNameRenamingTest.class)
+        .addKeepMainRule(MAIN)
+        .addKeepClassRulesWithAllowObfuscation(Boo.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("true")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject boo = inspector.clazz(Boo.class);
+    assertThat(boo, isPresent());
+    assertThat(boo, isRenamed());
+
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+
+    MethodSubject mainMethod = main.mainMethod();
+    assertThat(mainMethod, isPresent());
+
+    assertTrue(
+        mainMethod.streamInstructions()
+            .noneMatch(i -> i.isConstString(
+                "com.android.tools.r8.naming.ForNameRenamingTest$Boo", JumboStringMode.ALLOW)));
+    assertTrue(
+        mainMethod.streamInstructions()
+            .anyMatch(i -> i.isConstString(boo.getFinalName(), JumboStringMode.ALLOW)));
+  }
+
+  static class Main {
+    public static void main(String... args) throws Exception {
+      Class<?> a = Class.forName("com.android.tools.r8.naming.ForNameRenamingTest$Boo");
+      Class<?> b = Class.forName(
+          "com.android.tools.r8.naming.ForNameRenamingTest$Boo", true, Boo.class.getClassLoader());
+      System.out.println(a.getSimpleName().equals(b.getSimpleName()));
+    }
+  }
+
+  static class Boo {}
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 2cd766b..e9383ef 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 import static com.android.tools.r8.utils.DescriptorUtils.isValidJavaType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -18,13 +20,10 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -129,19 +128,17 @@
   private static void test1_rules(
       CodeInspector inspector, int countInMain, int countInABar, int countInAFields) {
     ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
-    MethodSubject main = mainClass.method(CodeInspector.MAIN);
-    assertTrue(main instanceof FoundMethodSubject);
-    FoundMethodSubject foundMain = (FoundMethodSubject) main;
-    verifyPresenceOfConstString(foundMain);
-    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    MethodSubject main = mainClass.mainMethod();
+    assertThat(main, isPresent());
+    verifyPresenceOfConstString(main);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, main);
     assertEquals(countInMain, renamedYetFoundIdentifierCount);
 
     ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
-    MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
-    assertTrue(bar instanceof FoundMethodSubject);
-    FoundMethodSubject foundBar = (FoundMethodSubject) bar;
-    verifyPresenceOfConstString(foundBar);
-    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundBar);
+    MethodSubject bar = aClass.uniqueMethodWithName("bar");
+    assertThat(bar, isPresent());
+    verifyPresenceOfConstString(bar);
+    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, bar);
     assertEquals(countInABar, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
@@ -151,64 +148,57 @@
 
   private static void test_atomicfieldupdater(TestParameters parameters, CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("atomicfieldupdater.Main");
-    MethodSubject main = mainClass.method(CodeInspector.MAIN);
-    assertTrue(main instanceof FoundMethodSubject);
-    FoundMethodSubject foundMain = (FoundMethodSubject) main;
-    verifyPresenceOfConstString(foundMain);
+    MethodSubject main = mainClass.mainMethod();
+    assertThat(main, isPresent());
+    verifyPresenceOfConstString(main);
 
     ClassSubject a = inspector.clazz("atomicfieldupdater.A");
     Set<InstructionSubject> constStringInstructions =
-        getRenamedMemberIdentifierConstStrings(a, foundMain);
+        getRenamedMemberIdentifierConstStrings(a, main);
     assertEquals(3, constStringInstructions.size());
   }
 
   private static void test_forname(TestParameters parameters, CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("forname.Main");
-    MethodSubject main = mainClass.method(CodeInspector.MAIN);
-    assertTrue(main instanceof FoundMethodSubject);
-    FoundMethodSubject foundMain = (FoundMethodSubject) main;
-    verifyPresenceOfConstString(foundMain);
-    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    MethodSubject main = mainClass.mainMethod();
+    assertThat(main, isPresent());
+    verifyPresenceOfConstString(main);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, main);
     assertEquals(1, renamedYetFoundIdentifierCount);
   }
 
   private static void test_getmembers(TestParameters parameters, CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("getmembers.Main");
-    MethodSubject main = mainClass.method(CodeInspector.MAIN);
-    assertTrue(main instanceof FoundMethodSubject);
-    FoundMethodSubject foundMain = (FoundMethodSubject) main;
-    verifyPresenceOfConstString(foundMain);
+    MethodSubject main = mainClass.mainMethod();
+    assertThat(main, isPresent());
+    verifyPresenceOfConstString(main);
 
     ClassSubject a = inspector.clazz("getmembers.A");
     Set<InstructionSubject> constStringInstructions =
-        getRenamedMemberIdentifierConstStrings(a, foundMain);
+        getRenamedMemberIdentifierConstStrings(a, main);
     assertEquals(2, constStringInstructions.size());
 
     ClassSubject b = inspector.clazz("getmembers.B");
-    MethodSubject inliner = b.method("java.lang.String", "inliner", ImmutableList.of());
-    assertTrue(inliner instanceof FoundMethodSubject);
-    FoundMethodSubject foundInliner = (FoundMethodSubject) inliner;
-    constStringInstructions = getRenamedMemberIdentifierConstStrings(a, foundInliner);
+    MethodSubject inliner = b.uniqueMethodWithName("inliner");
+    assertThat(inliner, isPresent());
+    constStringInstructions = getRenamedMemberIdentifierConstStrings(a, inliner);
     assertEquals(1, constStringInstructions.size());
   }
 
   // Without -identifiernamestring
   private static void test2_rule1(TestParameters parameters, CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
-    MethodSubject main = mainClass.method(CodeInspector.MAIN);
-    assertTrue(main instanceof FoundMethodSubject);
-    FoundMethodSubject foundMain = (FoundMethodSubject) main;
-    verifyPresenceOfConstString(foundMain);
-    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    MethodSubject main = mainClass.mainMethod();
+    assertThat(main, isPresent());
+    verifyPresenceOfConstString(main);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, main);
     assertEquals(1, renamedYetFoundIdentifierCount);
 
     ClassSubject aClass = inspector.clazz("identifiernamestring.A");
-    MethodSubject aInit =
-        aClass.method("void", "<init>", ImmutableList.of());
-    assertTrue(aInit instanceof FoundMethodSubject);
-    FoundMethodSubject foundAInit = (FoundMethodSubject) aInit;
-    verifyPresenceOfConstString(foundAInit);
-    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundAInit);
+    MethodSubject aInit = aClass.init();
+    assertThat(aInit, isPresent());
+    verifyPresenceOfConstString(aInit);
+    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, aInit);
     assertEquals(0, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
@@ -219,20 +209,17 @@
   // With -identifiernamestring for annotations and name-based filters
   private static void test2_rule2(TestParameters parameters, CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
-    MethodSubject main = mainClass.method(CodeInspector.MAIN);
-    assertTrue(main instanceof FoundMethodSubject);
-    FoundMethodSubject foundMain = (FoundMethodSubject) main;
-    verifyPresenceOfConstString(foundMain);
-    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    MethodSubject main = mainClass.mainMethod();
+    assertThat(main, isPresent());
+    verifyPresenceOfConstString(main);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, main);
     assertEquals(parameters.isCfRuntime() ? 2 : 1, renamedYetFoundIdentifierCount);
 
     ClassSubject aClass = inspector.clazz("identifiernamestring.A");
-    MethodSubject aInit =
-        aClass.method("void", "<init>", ImmutableList.of());
-    assertTrue(aInit instanceof FoundMethodSubject);
-    FoundMethodSubject foundAInit = (FoundMethodSubject) aInit;
-    verifyPresenceOfConstString(foundAInit);
-    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundAInit);
+    MethodSubject aInit = aClass.init();
+    assertThat(aInit, isPresent());
+    verifyPresenceOfConstString(aInit);
+    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, aInit);
     assertEquals(1, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
@@ -243,31 +230,30 @@
   // With -identifiernamestring for reflective methods in testing class R.
   private static void test2_rule3(TestParameters parameters, CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
-    MethodSubject main = mainClass.method(CodeInspector.MAIN);
-    assertTrue(main instanceof FoundMethodSubject);
-    FoundMethodSubject foundMain = (FoundMethodSubject) main;
-    verifyPresenceOfConstString(foundMain);
+    MethodSubject main = mainClass.mainMethod();
+    assertThat(main, isPresent());
+    verifyPresenceOfConstString(main);
 
     ClassSubject b = inspector.clazz("identifiernamestring.B");
     Set<InstructionSubject> constStringInstructions =
-        getRenamedMemberIdentifierConstStrings(b, foundMain);
+        getRenamedMemberIdentifierConstStrings(b, main);
     assertEquals(2, constStringInstructions.size());
   }
 
-  private static void verifyPresenceOfConstString(FoundMethodSubject method) {
+  private static void verifyPresenceOfConstString(MethodSubject method) {
     assertTrue(
         method
             .iterateInstructions(instruction -> instruction.isConstString(JumboStringMode.ALLOW))
             .hasNext());
   }
 
-  private static Stream<InstructionSubject> getConstStringInstructions(FoundMethodSubject method) {
-    return Streams.stream(method.iterateInstructions())
+  private static Stream<InstructionSubject> getConstStringInstructions(MethodSubject method) {
+    return method.streamInstructions()
         .filter(instr -> instr.isConstString(JumboStringMode.ALLOW));
   }
 
   private static int countRenamedClassIdentifier(
-      CodeInspector inspector, FoundMethodSubject method) {
+      CodeInspector inspector, MethodSubject method) {
     return getConstStringInstructions(method)
         .reduce(
             0,
@@ -307,7 +293,7 @@
   }
 
   private static Set<InstructionSubject> getRenamedMemberIdentifierConstStrings(
-      ClassSubject clazz, FoundMethodSubject method) {
+      ClassSubject clazz, MethodSubject method) {
     Set<InstructionSubject> result = Sets.newIdentityHashSet();
     getConstStringInstructions(method)
         .forEach(
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
index cb4bed0..6887f59 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
@@ -40,7 +40,6 @@
   }
 
   @Test
-  @Ignore("b/142909857")
   public void testNotRenamingClInitIfNotInMap()
       throws ExecutionException, CompilationFailedException, IOException {
     testForR8(parameters.getBackend())
@@ -54,7 +53,6 @@
   }
 
   @Test
-  @Ignore("b/142909857")
   public void testNotRenamingClInitIfInMap()
       throws ExecutionException, CompilationFailedException, IOException {
     String interfaceName = TestInterface.class.getTypeName();
diff --git a/tools/historic_memory_usage.py b/tools/historic_memory_usage.py
index 376426f..bb08a2e 100755
--- a/tools/historic_memory_usage.py
+++ b/tools/historic_memory_usage.py
@@ -10,13 +10,13 @@
 # It will then run the oldest and newest such commit, and gradually fill in
 # the commits in between.
 
+import historic_run
 import optparse
 import os
 import subprocess
 import sys
 import utils
 
-MASTER_COMMITS = 'gs://r8-releases/raw/master'
 APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome']
 COMPILERS = ['d8', 'r8']
 
@@ -31,7 +31,7 @@
                     default='gmail',
                     choices=APPS)
   result.add_option('--top',
-                    default=utils.get_HEAD_sha1(),
+                    default=historic_run.top_or_default(),
                     help='The most recent commit to test')
   result.add_option('--bottom',
                     help='The oldest commit to test')
@@ -44,84 +44,8 @@
                     help='Set timeout instead of waiting for OOM.')
   return result.parse_args(argv)
 
-
-class GitCommit(object):
-  def __init__(self, git_hash, destination_dir, destination, timestamp):
-    self.git_hash = git_hash
-    self.destination_dir = destination_dir
-    self.destination = destination
-    self.timestamp = timestamp
-
-  def __str__(self):
-    return '%s : %s (%s)' % (self.git_hash, self.destination, self.timestamp)
-
-  def __repr__(self):
-    return self.__str__()
-
-def git_commit_from_hash(hash):
-  commit_timestamp = subprocess.check_output(['git', 'show', '--no-patch',
-                                         '--no-notes', '--pretty=\'%ct\'',
-                                         hash]).strip().strip('\'')
-  destination_dir = '%s/%s/' % (MASTER_COMMITS, hash)
-  destination = '%s%s' % (destination_dir, 'r8.jar')
-  commit = GitCommit(hash, destination_dir, destination, commit_timestamp)
-  return commit
-
-def enumerate_git_commits(options):
-  top = options.top if options.top else utils.get_HEAD_sha1()
-  # TODO(ricow): if not set, search back 1000
-  if not options.bottom:
-    raise Exception('No bottom specified')
-  bottom = options.bottom
-  output = subprocess.check_output(['git', 'rev-list', '--first-parent', top])
-  found_bottom = False
-  commits = []
-  for c in output.splitlines():
-    commits.append(git_commit_from_hash(c.strip()))
-    if c.strip() == bottom:
-      found_bottom = True
-      break
-  if not found_bottom:
-    raise Exception('Bottom not found, did you not use a merge commit')
-  return commits
-
-def get_available_commits(commits):
-  cloud_commits = subprocess.check_output(['gsutil.py', 'ls', MASTER_COMMITS]).splitlines()
-  available_commits = []
-  for commit in commits:
-    if commit.destination_dir in cloud_commits:
-      available_commits.append(commit)
-  return available_commits
-
-def print_commits(commits):
-  for commit in commits:
-    print(commit)
-
-def permutate_range(start, end):
-  diff = end - start
-  assert diff >= 0
-  if diff == 1:
-    return [start, end]
-  if diff == 0:
-    return [start]
-  half = end - (diff / 2)
-  numbers = [half]
-  first_half = permutate_range(start, half - 1)
-  second_half = permutate_range(half + 1, end)
-  for index in range(len(first_half)):
-    numbers.append(first_half[index])
-    if index < len(second_half):
-      numbers.append(second_half[index])
-  return numbers
-
-def permutate(number_of_commits):
-  assert number_of_commits > 0
-  numbers = permutate_range(0, number_of_commits - 1)
-  assert all(n in numbers for n in range(number_of_commits))
-  return numbers
-
-def pull_r8_from_cloud(commit):
-  utils.download_file_from_cloud_storage(commit.destination, utils.R8_JAR)
+def make_run_on_app_command(options):
+  return lambda commit: run_on_app(options, commit)
 
 def run_on_app(options, commit):
   app = options.app
@@ -142,30 +66,14 @@
     f.write(stdout)
   print('Wrote stdout to: %s' % stdout_path)
 
-
-def benchmark(commits, options):
-  commit_permutations = permutate(len(commits))
-  count = 0
-  for index in commit_permutations:
-    count += 1
-    print('Running commit %s out of %s' % (count, len(commits)))
-    commit = commits[index]
-    if not utils.cloud_storage_exists(commit.destination):
-      # We may have a directory, but no r8.jar
-      continue
-    pull_r8_from_cloud(commit)
-    print('Running for commit: %s' % commit.git_hash)
-    run_on_app(options, commit)
-
 def main(argv):
   (options, args) = ParseOptions(argv)
   if not options.app:
      raise Exception('Please specify an app')
-  commits = enumerate_git_commits(options)
-  available_commits = get_available_commits(commits)
-  print('Running for:')
-  print_commits(available_commits)
-  benchmark(available_commits, options)
+  top = historic_run.top_or_default(options.top)
+  bottom = historic_run.bottom_or_default(options.bottom)
+  command = make_run_on_app_command(options)
+  historic_run.run(command, top, bottom)
 
 if __name__ == '__main__':
   sys.exit(main(sys.argv[1:]))
diff --git a/tools/historic_run.py b/tools/historic_run.py
new file mode 100755
index 0000000..b0f181f
--- /dev/null
+++ b/tools/historic_run.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, 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.
+
+# Convenience script for running a command over builds back in time. This
+# utilizes the prebuilt full r8 jars on cloud storage.  The script find all
+# commits that exists on cloud storage in the given range.  It will then run the
+# oldest and newest such commit, and gradually fill in the commits in between.
+
+import optparse
+import os
+import subprocess
+import sys
+import time
+import utils
+
+MASTER_COMMITS = 'gs://r8-releases/raw/master'
+
+def ParseOptions(argv):
+  result = optparse.OptionParser()
+  result.add_option(
+    '--cmd',
+    help='Command to run')
+  result.add_option(
+    '--top',
+    default=top_or_default(),
+    help='The most recent commit to test')
+  result.add_option(
+    '--bottom',
+    help='The oldest commit to test')
+  result.add_option(
+    '--dry-run',
+    help='Do not download or run the command, but print the actions',
+    default=False,
+    action='store_true')
+  result.add_option(
+    '--output',
+    default='build',
+    help='Directory where to output results')
+  return result.parse_args(argv)
+
+
+class GitCommit(object):
+  def __init__(self, git_hash, destination_dir, destination, timestamp):
+    self.git_hash = git_hash
+    self.destination_dir = destination_dir
+    self.destination = destination
+    self.timestamp = timestamp
+
+  def __str__(self):
+    return '%s : %s (%s)' % (self.git_hash, self.destination, self.timestamp)
+
+  def __repr__(self):
+    return self.__str__()
+
+def git_commit_from_hash(hash):
+  commit_timestamp = subprocess.check_output(['git', 'show', '--no-patch',
+                                         '--no-notes', '--pretty=\'%ct\'',
+                                         hash]).strip().strip('\'')
+  destination_dir = '%s/%s/' % (MASTER_COMMITS, hash)
+  destination = '%s%s' % (destination_dir, 'r8.jar')
+  commit = GitCommit(hash, destination_dir, destination, commit_timestamp)
+  return commit
+
+def enumerate_git_commits(top, bottom):
+  output = subprocess.check_output(['git', 'rev-list', '--first-parent', top])
+  found_bottom = False
+  commits = []
+  for c in output.splitlines():
+    commit_hash = c.strip()
+    commits.append(git_commit_from_hash(commit_hash))
+    if commit_hash == bottom:
+      found_bottom = True
+      break
+  if not found_bottom:
+    raise Exception('Bottom not found, did you not use a merge commit')
+  return commits
+
+def get_available_commits(commits):
+  cloud_commits = subprocess.check_output(
+    ['gsutil.py', 'ls', MASTER_COMMITS]).splitlines()
+  available_commits = []
+  for commit in commits:
+    if commit.destination_dir in cloud_commits:
+      available_commits.append(commit)
+  return available_commits
+
+def print_commits(commits):
+  for commit in commits:
+    print(commit)
+
+def permutate_range(start, end):
+  diff = end - start
+  assert diff >= 0
+  if diff == 1:
+    return [start, end]
+  if diff == 0:
+    return [start]
+  half = end - (diff / 2)
+  numbers = [half]
+  first_half = permutate_range(start, half - 1)
+  second_half = permutate_range(half + 1, end)
+  for index in range(len(first_half)):
+    numbers.append(first_half[index])
+    if index < len(second_half):
+      numbers.append(second_half[index])
+  return numbers
+
+def permutate(number_of_commits):
+  assert number_of_commits > 0
+  numbers = permutate_range(0, number_of_commits - 1)
+  assert all(n in numbers for n in range(number_of_commits))
+  return numbers
+
+def pull_r8_from_cloud(commit):
+  utils.download_file_from_cloud_storage(commit.destination, utils.R8_JAR)
+
+def benchmark(commits, command, dryrun=False):
+  commit_permutations = permutate(len(commits))
+  count = 0
+  for index in commit_permutations:
+    count += 1
+    print('Running commit %s out of %s' % (count, len(commits)))
+    commit = commits[index]
+    if not utils.cloud_storage_exists(commit.destination):
+      # We may have a directory, but no r8.jar
+      continue
+    if not dryrun:
+      pull_r8_from_cloud(commit)
+    print('Running for commit: %s' % commit.git_hash)
+    command(commit)
+
+def top_or_default(top=None):
+  return top if top else utils.get_HEAD_sha1()
+
+def bottom_or_default(bottom=None):
+  # TODO(ricow): if not set, search back 1000
+  if not bottom:
+    raise Exception('No bottom specified')
+  return bottom
+
+def run(command, top, bottom, dryrun=False):
+  commits = enumerate_git_commits(top, bottom)
+  available_commits = get_available_commits(commits)
+  print('Running for:')
+  print_commits(available_commits)
+  benchmark(available_commits, command, dryrun=dryrun)
+
+def make_cmd(options):
+  return lambda commit: run_cmd(options, commit)
+
+def run_cmd(options, commit):
+  cmd = [options.cmd, commit.git_hash]
+  output_path = options.output or 'build'
+  time_commit = '%s_%s' % (commit.timestamp, commit.git_hash)
+  time_commit_path = os.path.join(output_path, time_commit)
+  print ' '.join(cmd)
+  if not options.dry_run:
+    if not os.path.exists(time_commit_path):
+      os.makedirs(time_commit_path)
+    stdout_path = os.path.join(time_commit_path, 'stdout')
+    stderr_path = os.path.join(time_commit_path, 'stderr')
+    with open(stdout_path, 'w') as stdout:
+      with open(stderr_path, 'w') as stderr:
+        process = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
+        timeout = 1000
+        while process.poll() is None and timeout > 0:
+          time.sleep(1)
+          timeout -= 1
+        if process.poll() is None:
+          process.kill()
+          print "Task timed out"
+          stderr.write("timeout\n")
+  print('Wrote outputs to: %s' % time_commit_path)
+
+def main(argv):
+  (options, args) = ParseOptions(argv)
+  if not options.cmd:
+     raise Exception('Please specify a command')
+  top = top_or_default(options.top)
+  bottom = bottom_or_default(options.bottom)
+  command = make_cmd(options)
+  run(command, top, bottom, dryrun=options.dry_run)
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/internal_test.py b/tools/internal_test.py
index c85942b..d761a98 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -36,6 +36,7 @@
 import sys
 import time
 import utils
+import run_on_app
 
 # How often the bot/tester should check state
 PULL_DELAY = 30
@@ -265,6 +266,8 @@
         gs_location = '%s%s' % (entry, to_print)
         value = utils.cat_file_on_cloud_storage(gs_location)
         print('\n\n%s had value:\n%s' % (to_print, value))
+  print("\n\nPrinting find-min-xmx ranges for apps")
+  run_on_app.print_min_xmx_ranges_for_hash(hash, 'r8', 'lib')
 
 def run_bot():
   print_magic_file_state()
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
index 8b086e5..cdbab78 100755
--- a/tools/minify_tool.py
+++ b/tools/minify_tool.py
@@ -87,7 +87,7 @@
 
 def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None,
                 lib=utils.RT_JAR, debug=True, build=True, benchmark_name=None,
-                track_memory_file=None):
+                track_memory_file=None, additional_args=[]):
   if output_jar is None:
     output_jar = generate_output_name(input_jar, mainclass)
   with utils.TempDir() as path:
@@ -100,12 +100,12 @@
     keep_path = os.path.join(path, 'keep.txt')
     with open(keep_path, 'w') as fp:
       fp.write(KEEP % mainclass)
-    args = ('--lib', lib,
+    args = ['--lib', lib,
             '--classfile',
             '--output', output_jar,
             '--pg-conf', keep_path,
             '--release',
-            tmp_input_path)
+            tmp_input_path] + additional_args
     start_time = time.time()
     return_code = toolhelper.run('r8', args, debug=debug, build=build,
                                  track_memory_file=track_memory_file)
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 4d9273c..1d3cfbb 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -23,7 +23,7 @@
   with utils.ChangedWorkingDirectory(path):
     subprocess.call(['repo', 'abandon', 'update-r8'])
     if not options.no_sync:
-      subprocess.check_call(['repo', 'sync'])
+      subprocess.check_call(['repo', 'sync', '-cq', '-j', '16'])
 
     prebuilts_r8 = os.path.join(path, 'prebuilts', 'r8')
 
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
index 90834f8..e12051b 100755
--- a/tools/run_bootstrap_benchmark.py
+++ b/tools/run_bootstrap_benchmark.py
@@ -66,4 +66,15 @@
         utils.PINNED_PGR8_JAR)
     print "BootstrapR8PGDex(CodeSize):", utils.uncompressed_size(d8_pg_output)
 
+    r8_notreeshaking_output = os.path.join(temp, 'r8-notreeshaking.zip')
+    return_code = minify_tool.minify_tool(
+      input_jar=utils.PINNED_R8_JAR,
+      output_jar=r8_notreeshaking_output,
+      debug=False,
+      build=False,
+      benchmark_name="BootstrapR8NoTreeShaking",
+      additional_args=["--no-tree-shaking"])
+    if return_code != 0:
+      sys.exit(return_code)
+
   sys.exit(0)
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index b32581b..426fef4 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -272,6 +272,21 @@
 
   return 0
 
+def print_min_xmx_ranges_for_hash(hash, compiler, compiler_build):
+  app_directory = os.path.join(
+      utils.R8_TEST_RESULTS_BUCKET,
+      FIND_MIN_XMX_DIR,
+      hash,
+      compiler,
+      compiler_build)
+  gs_base = 'gs://%s' % app_directory
+  for app in utils.ls_files_on_cloud_storage(gs_base).strip().split('\n'):
+    for version in utils.ls_files_on_cloud_storage(app).strip().split('\n'):
+      for type in utils.ls_files_on_cloud_storage(version).strip().split('\n'):
+        gs_location = '%s%s' % (type, FIND_MIN_XMX_FILE)
+        value = utils.cat_file_on_cloud_storage(gs_location, ignore_errors=True)
+        print('%s\n' % value)
+
 def main(argv):
   (options, args) = ParseOptions(argv)
   if options.expect_oom and not options.max_memory: