Merge "Revert "Disallow const instructions after throwing instructions""
diff --git a/build.gradle b/build.gradle
index c205b2d..33d967f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1443,8 +1443,13 @@
         def out = new StringBuffer()
         def err = new StringBuffer()
         def command = "tools/retrace.py"
+        def header = "RETRACED STACKTRACE";
+        if (System.getenv('BUILDBOT_BUILDERNAME') != null
+                && !System.getenv('BUILDBOT_BUILDERNAME').endsWith("_release")) {
+            header += ": (${command} --commit_hash ${System.getenv('BUILDBOT_REVISION')})";
+        }
         out.append("\n--------------------------------------\n")
-        out.append("RETRACED STACKTRACE\n")
+        out.append("${header}\n")
         out.append("--------------------------------------\n")
         Process process = command.execute()
         def processIn = new PrintStream(process.getOut())
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index ebeae02..63c1cde 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -45,11 +45,12 @@
     return NAMES;
   }
 
-  protected int flags;
-  protected boolean isPublicized = false;
+  protected int originalFlags;
+  protected int modifiedFlags;
 
-  protected AccessFlags(int flags) {
-    this.flags = flags;
+  protected AccessFlags(int originalFlags, int modifiedFlags) {
+    this.originalFlags = originalFlags;
+    this.modifiedFlags = modifiedFlags;
   }
 
   public abstract T copy();
@@ -57,10 +58,7 @@
   public abstract T self();
 
   public int materialize() {
-    if (isPromotedToPublic()) {
-      return ((flags | Constants.ACC_PUBLIC) & ~Constants.ACC_PROTECTED) & ~Constants.ACC_PRIVATE;
-    }
-    return flags;
+    return modifiedFlags;
   }
 
   public abstract int getAsCfAccessFlags();
@@ -68,21 +66,21 @@
   public abstract int getAsDexAccessFlags();
 
   public final int getOriginalCfAccessFlags() {
-    return flags;
+    return originalFlags;
   }
 
   @Override
   public boolean equals(Object object) {
     if (object instanceof AccessFlags) {
       AccessFlags other = (AccessFlags) object;
-      return flags == other.flags && isPublicized == other.isPublicized;
+      return originalFlags == other.originalFlags && modifiedFlags == other.modifiedFlags;
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return (flags << 1) | (isPublicized ? 1 : 0);
+    return originalFlags | modifiedFlags;
   }
 
   public boolean isMoreVisibleThan(AccessFlags other) {
@@ -109,7 +107,7 @@
   }
 
   public boolean isPublic() {
-    return isSet(Constants.ACC_PUBLIC) || isPromotedToPublic();
+    return isSet(Constants.ACC_PUBLIC);
   }
 
   public void setPublic() {
@@ -122,7 +120,7 @@
   }
 
   public boolean isPrivate() {
-    return isSet(Constants.ACC_PRIVATE) && !isPromotedToPublic();
+    return isSet(Constants.ACC_PRIVATE);
   }
 
   public void setPrivate() {
@@ -135,7 +133,7 @@
   }
 
   public boolean isProtected() {
-    return isSet(Constants.ACC_PROTECTED) && !isPromotedToPublic();
+    return isSet(Constants.ACC_PROTECTED);
   }
 
   public void setProtected() {
@@ -179,35 +177,56 @@
     unset(Constants.ACC_SYNTHETIC);
   }
 
-  public boolean isPromotedToPublic() {
-    return isPublicized;
+  public void promoteToFinal() {
+    promote(Constants.ACC_FINAL);
   }
 
-  public T setPromotedToPublic(boolean isPublicized) {
-    this.isPublicized = isPublicized;
-    return self();
+  public void demoteFromFinal() {
+    demote(Constants.ACC_FINAL);
+  }
+
+  public boolean isPromotedToPublic() {
+    return isPromoted(Constants.ACC_PUBLIC);
   }
 
   public void promoteToPublic() {
-    isPublicized = true;
+    demote(Constants.ACC_PRIVATE | Constants.ACC_PROTECTED);
+    promote(Constants.ACC_PUBLIC);
   }
 
-  public void unsetPromotedToPublic() {
-    isPublicized = false;
+  public void promoteToStatic() {
+    promote(Constants.ACC_STATIC);
+  }
+
+  private boolean wasSet(int flag) {
+    return (originalFlags & flag) != 0;
   }
 
   protected boolean isSet(int flag) {
-    return (flags & flag) != 0;
+    return (modifiedFlags & flag) != 0;
   }
 
   protected void set(int flag) {
-    flags |= flag;
+    originalFlags |= flag;
+    modifiedFlags |= flag;
   }
 
   protected void unset(int flag) {
-    flags &= ~flag;
+    originalFlags &= ~flag;
+    modifiedFlags &= ~flag;
   }
 
+  protected boolean isPromoted(int flag) {
+    return !wasSet(flag) && isSet(flag);
+  }
+
+  protected void promote(int flag) {
+    modifiedFlags |= flag;
+  }
+
+  protected void demote(int flag) {
+    modifiedFlags &= ~flag;
+  }
 
   public String toSmaliString() {
     return toStringInternal(true);
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a45064e..e415b0f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -119,8 +119,10 @@
 
   // For mapping invoke virtual instruction to target methods.
   public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
-    Set<DexEncodedMethod> result = new HashSet<>();
-    // First add the target for receiver type method.type.
+    if (method.holder.isArrayType()) {
+      assert method.name == dexItemFactory.cloneMethodName;
+      return null;
+    }
     DexClass root = definitionFor(method.holder);
     if (root == null) {
       // type specified in method does not have a materialized class.
@@ -131,6 +133,8 @@
       // This will fail at runtime.
       return null;
     }
+    // First add the target for receiver type method.type.
+    Set<DexEncodedMethod> result = new HashSet<>();
     topTargets.forEachTarget(result::add);
     // Add all matching targets from the subclass hierarchy.
     for (DexType type : subtypes(method.holder)) {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 6c7caf9..0c96120 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -50,7 +50,11 @@
   }
 
   private ClassAccessFlags(int flags) {
-    super(flags);
+    this(flags, flags);
+  }
+
+  private ClassAccessFlags(int originalFlags, int modifiedFlags) {
+    super(originalFlags, modifiedFlags);
   }
 
   public static ClassAccessFlags fromSharedAccessFlags(int access) {
@@ -70,7 +74,7 @@
 
   @Override
   public ClassAccessFlags copy() {
-    return new ClassAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+    return new ClassAccessFlags(originalFlags, modifiedFlags);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 5aa2ee4..44294db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -645,7 +645,7 @@
     // for the forwarding method, as the forwarding method will copy the access flags from this,
     // and if different forwarding methods are created in different subclasses the first could be
     // final.
-    accessFlags.unsetFinal();
+    accessFlags.demoteFromFinal();
     DexMethod newMethod = itemFactory.createMethod(holder.type, method.proto, method.name);
     Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER;
     Builder builder = builder(this);
@@ -685,7 +685,8 @@
   public DexEncodedMethod toStaticMethodWithoutThis() {
     checkIfObsolete();
     assert !accessFlags.isStatic();
-    Builder builder = builder(this).setStatic().unsetOptimizationInfo().withoutThisParameter();
+    Builder builder =
+        builder(this).promoteToStatic().unsetOptimizationInfo().withoutThisParameter();
     setObsolete();
     return builder.build();
   }
@@ -1255,8 +1256,8 @@
       this.method = method;
     }
 
-    public Builder setStatic() {
-      this.accessFlags.setStatic();
+    public Builder promoteToStatic() {
+      this.accessFlags.promoteToStatic();
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 012c4a4..1e125d9 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -45,6 +45,7 @@
 
   @Override
   public DexClass definitionFor(DexType type) {
+    assert type.isClassType() : "Cannot lookup definition for type: " + type;
     DexClass result = programClasses.get(type);
     if (result == null) {
       result = libraryClasses.get(type);
@@ -73,22 +74,24 @@
   }
 
   public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
-    assert mappingIsValid(graphLense, programClasses.getAllTypes());
-    assert mappingIsValid(graphLense, libraryClasses.keySet());
     // As a side effect, this will rebuild the program classes and library classes maps.
-    return this.builder().build().asDirect();
+    DirectMappedDexApplication rewrittenApplication = this.builder().build().asDirect();
+    assert rewrittenApplication.mappingIsValid(graphLense, programClasses.getAllTypes());
+    assert rewrittenApplication.mappingIsValid(graphLense, libraryClasses.keySet());
+    return rewrittenApplication;
   }
 
   private boolean mappingIsValid(GraphLense graphLense, Iterable<DexType> types) {
-    // The lense might either map to a different type that is already present in the application
+    // The lens might either map to a different type that is already present in the application
     // (e.g. relinking a type) or it might encode a type that was renamed, in which case the
     // original type will point to a definition that was renamed.
     for (DexType type : types) {
       DexType renamed = graphLense.lookupType(type);
       if (renamed != type) {
-        if (definitionFor(type).type != renamed && definitionFor(renamed) == null) {
-          return false;
+        if (definitionFor(type) == null && definitionFor(renamed) != null) {
+          continue;
         }
+        assert definitionFor(type).type == renamed || definitionFor(renamed) != null;
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index e940269..bb39765 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -37,12 +37,16 @@
   }
 
   private FieldAccessFlags(int flags) {
-    super(flags);
+    this(flags, flags);
+  }
+
+  private FieldAccessFlags(int originalFlags, int modifiedFlags) {
+    super(originalFlags, modifiedFlags);
   }
 
   @Override
   public FieldAccessFlags copy() {
-    return new FieldAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+    return new FieldAccessFlags(originalFlags, modifiedFlags);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 898afed..bc22da0 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -359,6 +359,8 @@
     return new Builder();
   }
 
+  public abstract DexType getOriginalType(DexType type);
+
   public abstract DexField getOriginalFieldSignature(DexField field);
 
   public abstract DexMethod getOriginalMethodSignature(DexMethod method);
@@ -391,7 +393,7 @@
   }
 
   public abstract GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type);
+      DexMethod method, DexMethod context, Type type);
 
   public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method);
 
@@ -626,6 +628,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      return type;
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       return field;
     }
@@ -651,8 +658,7 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       return new GraphLenseLookupResult(method, type);
     }
 
@@ -673,14 +679,14 @@
   }
 
   /**
-   * GraphLense implementation with a parent lense using a simple mapping for type, method and
-   * field mapping.
+   * GraphLense implementation with a parent lense using a simple mapping for type, method and field
+   * mapping.
    *
-   * Subclasses can override the lookup methods.
+   * <p>Subclasses can override the lookup methods.
    *
-   * For method mapping where invocation type can change just override
-   * {@link #mapInvocationType(DexMethod, DexMethod, DexEncodedMethod, Type)} if
-   * the default name mapping applies, and only invocation type might need to change.
+   * <p>For method mapping where invocation type can change just override {@link
+   * #mapInvocationType(DexMethod, DexMethod, DexMethod, Type)} if the default name mapping applies,
+   * and only invocation type might need to change.
    */
   public static class NestedGraphLense extends GraphLense {
 
@@ -715,6 +721,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      return previousLense.getOriginalType(type);
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       DexField originalField =
           originalFieldSignatures != null
@@ -772,9 +783,12 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
-      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+      DexMethod previousContext =
+          originalMethodSignatures != null
+              ? originalMethodSignatures.getOrDefault(context, context)
+              : context;
+      GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
       DexMethod newMethod = methodMap.get(previous.getMethod());
       if (newMethod == null) {
         return previous;
@@ -782,7 +796,7 @@
       // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
       // that only subclasses which are known to need it actually do it?
       return new GraphLenseLookupResult(
-          newMethod, mapInvocationType(newMethod, method, context, previous.getType()));
+          newMethod, mapInvocationType(newMethod, method, previous.getType()));
     }
 
     @Override
@@ -793,22 +807,20 @@
     /**
      * Default invocation type mapping.
      *
-     * This is an identity mapping. If a subclass need invocation type mapping either override
-     * this method or {@link #lookupMethod(DexMethod, DexEncodedMethod, Type)}
+     * <p>This is an identity mapping. If a subclass need invocation type mapping either override
+     * this method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
      */
-    protected Type mapInvocationType(
-        DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+    protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
       return type;
     }
 
     /**
      * Standard mapping between interface and virtual invoke type.
      *
-     * Handle methods moved from interface to class or class to interface.
+     * <p>Handle methods moved from interface to class or class to interface.
      */
-    final protected Type mapVirtualInterfaceInvocationTypes(
-        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod,
-        DexEncodedMethod context, Type type) {
+    protected final Type mapVirtualInterfaceInvocationTypes(
+        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod, Type type) {
       if (type == Type.VIRTUAL || type == Type.INTERFACE) {
         // Get the invoke type of the actual definition.
         DexClass newTargetClass = appInfo.definitionFor(newMethod.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 6dea825..b236428 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -42,9 +42,7 @@
 
   @Override
   public DexClass definitionFor(DexType type) {
-    if (type == null) {
-      return null;
-    }
+    assert type.isClassType() : "Cannot lookup definition for type: " + type;
     DexClass clazz = programClasses.get(type);
     if (clazz == null && classpathClasses != null) {
       clazz = classpathClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index ca507b7..2173ed0 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -54,12 +54,16 @@
   }
 
   private MethodAccessFlags(int flags) {
-    super(flags);
+    this(flags, flags);
+  }
+
+  private MethodAccessFlags(int originalFlags, int modifiedFlags) {
+    super(originalFlags, modifiedFlags);
   }
 
   @Override
   public MethodAccessFlags copy() {
-    return new MethodAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+    return new MethodAccessFlags(originalFlags, modifiedFlags);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 53a6245..75a9726 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -78,7 +78,7 @@
       return true;
     }
 
-    if (valueType.isReference()) {
+    if (fieldType.isClassType() && valueType.isReference()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
       DexClass clazz = appInfo.definitionFor(instruction.getField().type);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index bd57f29..7610f9c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -91,6 +91,9 @@
     // A const-class instruction can be dead code only if the resulting program is known to contain
     // the class mentioned.
     DexType baseType = clazz.toBaseType(appInfo.dexItemFactory);
+    if (baseType.isPrimitiveType()) {
+      return true;
+    }
     DexClass holder = appInfo.definitionFor(baseType);
     return holder != null && holder.isProgramClass();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index d9040c5..c12ea40 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -532,7 +532,7 @@
 
     private void processInvoke(Type type, DexMethod method) {
       DexEncodedMethod source = caller.method;
-      GraphLenseLookupResult result = graphLense.lookupMethod(method, source, type);
+      GraphLenseLookupResult result = graphLense.lookupMethod(method, source.method, type);
       method = result.getMethod();
       type = result.getType();
       DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7b55543..f068a6a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1290,6 +1290,7 @@
       CodeRewriter.collapseTrivialGotos(method, code);
       PeepholeOptimizer.optimize(code, registerAllocator);
     }
+    CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
     CodeRewriter.collapseTrivialGotos(method, code);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 62cddc9..7c22bbf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -156,7 +156,7 @@
             checkInvokeDirect(method.method, invoke.asInvokeDirect());
           }
           GraphLenseLookupResult lenseLookup =
-              graphLense.lookupMethod(invokedMethod, method, invoke.getType());
+              graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
           DexMethod actualTarget = lenseLookup.getMethod();
           Invoke.Type actualInvokeType = lenseLookup.getType();
           if (actualInvokeType == Type.VIRTUAL) {
@@ -306,10 +306,11 @@
           }
         } else if (current.isMoveException()) {
           MoveException moveException = current.asMoveException();
-          if (moveException.hasOutValue()) {
-            // Conservatively add the out-value to `newSSAValues` since the catch handler guards
-            // may have been renamed as a result of class merging.
-            newSSAValues.add(moveException.outValue());
+          DexType newExceptionType = graphLense.lookupType(moveException.getExceptionType());
+          if (newExceptionType != moveException.getExceptionType()) {
+            iterator.replaceCurrentInstruction(
+                new MoveException(
+                    makeOutValue(moveException, code, newSSAValues), newExceptionType, options));
           }
         } else if (current.isNewArrayEmpty()) {
           NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
@@ -320,7 +321,7 @@
             iterator.replaceCurrentInstruction(newNewArray);
           }
         } else if (current.isNewInstance()) {
-          NewInstance newInstance= current.asNewInstance();
+          NewInstance newInstance = current.asNewInstance();
           DexType newClazz = graphLense.lookupType(newInstance.clazz);
           if (newClazz != newInstance.clazz) {
             NewInstance newNewInstance = new NewInstance(
@@ -456,7 +457,7 @@
       DexMethod invokedMethod = methodHandle.asMethod();
       MethodHandleType oldType = methodHandle.type;
       GraphLenseLookupResult lenseLookup =
-          graphLense.lookupMethod(invokedMethod, context, oldType.toInvokeType());
+          graphLense.lookupMethod(invokedMethod, context.method, oldType.toInvokeType());
       DexMethod rewrittenTarget = lenseLookup.getMethod();
       DexMethod actualTarget;
       MethodHandleType newType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 6ba9b6a..4f335a6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -85,7 +85,7 @@
 
         MethodAccessFlags newFlags = virtual.accessFlags.copy();
         newFlags.unsetBridge();
-        newFlags.setStatic();
+        newFlags.promoteToStatic();
         DexCode dexCode = code.asDexCode();
         // We cannot name the parameter "this" because the debugger may omit it due to the method
         // actually being static. Instead we prepend it with a special character.
@@ -124,8 +124,7 @@
       MethodAccessFlags originalFlags = direct.accessFlags;
       MethodAccessFlags newFlags = originalFlags.copy();
       if (originalFlags.isPrivate()) {
-        newFlags.unsetPrivate();
-        newFlags.setPublic();
+        newFlags.promoteToPublic();
       }
 
       DexMethod oldMethod = direct.method;
@@ -143,7 +142,7 @@
           assert !rewriter.factory.isClassConstructor(oldMethod)
               : "Unexpected private constructor " + direct.toSourceString()
               + " in " + iface.origin;
-          newFlags.setStatic();
+          newFlags.promoteToStatic();
 
           DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(oldMethod);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
index 26f890e..d5ceeb1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
@@ -28,13 +27,12 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     if (methodMap.get(originalMethod) == newMethod) {
       assert type == Type.VIRTUAL || type == Type.DIRECT;
       return Type.STATIC;
     }
-    return super.mapInvocationType(newMethod, originalMethod, context, type);
+    return super.mapInvocationType(newMethod, originalMethod, type);
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 3227d45..1eae24e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -61,6 +61,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DebugLocalWrite;
+import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.Goto;
@@ -78,6 +79,7 @@
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -97,6 +99,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
+import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -115,12 +118,18 @@
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
 import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
@@ -2147,6 +2156,9 @@
 
   private boolean isTypeInaccessibleInCurrentContext(DexType type, DexEncodedMethod context) {
     DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    if (baseType.isPrimitiveType()) {
+      return false;
+    }
     DexClass clazz = definitionFor(baseType);
     if (clazz == null) {
       // Conservatively say yes.
@@ -3640,6 +3652,132 @@
     assert code.isConsistentSSA();
   }
 
+  /**
+   * Remove moves that are not actually used by instructions in exiting paths. These moves can arise
+   * due to debug local info needing a particular value and the live-interval for it then moves it
+   * back into the properly assigned register. If the register is only used for debug purposes, it
+   * is safe to just remove the move and update the local information accordingly.
+   */
+  public static void removeUnneededMovesOnExitingPaths(
+      IRCode code, LinearScanRegisterAllocator allocator) {
+    if (!code.options.debug) {
+      return;
+    }
+    for (BasicBlock block : code.blocks) {
+      // Skip non-exit blocks.
+      if (!block.getSuccessors().isEmpty()) {
+        continue;
+      }
+      // Skip blocks with no locals at entry.
+      Int2ReferenceMap<DebugLocalInfo> localsAtEntry = block.getLocalsAtEntry();
+      if (localsAtEntry == null || localsAtEntry.isEmpty()) {
+        continue;
+      }
+      // Find the locals state after spill moves.
+      DebugLocalsChange postSpillLocalsChange = null;
+      for (Instruction instruction : block.getInstructions()) {
+        if (instruction.getNumber() != -1 || postSpillLocalsChange != null) {
+          break;
+        }
+        postSpillLocalsChange = instruction.asDebugLocalsChange();
+      }
+      // Skip if the locals state did not change.
+      if (postSpillLocalsChange == null
+          || !postSpillLocalsChange.apply(new Int2ReferenceOpenHashMap<>(localsAtEntry))) {
+        continue;
+      }
+      // Collect the moves that can safely be removed.
+      Set<Move> unneededMoves = computeUnneededMoves(block, postSpillLocalsChange, allocator);
+      if (unneededMoves.isEmpty()) {
+        continue;
+      }
+      Int2IntMap previousMapping = new Int2IntOpenHashMap();
+      Int2IntMap mapping = new Int2IntOpenHashMap();
+      ListIterator<Instruction> it = block.getInstructions().listIterator();
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        if (instruction.isMove()) {
+          Move move = instruction.asMove();
+          if (unneededMoves.contains(move)) {
+            int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+            int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+            int mappedSrc = mapping.getOrDefault(src, src);
+            mapping.put(dst, mappedSrc);
+            it.remove();
+          }
+        } else if (instruction.isDebugLocalsChange()) {
+          DebugLocalsChange change = instruction.asDebugLocalsChange();
+          updateDebugLocalsRegisterMap(previousMapping, change.getEnding());
+          updateDebugLocalsRegisterMap(mapping, change.getStarting());
+          previousMapping = mapping;
+          mapping = new Int2IntOpenHashMap(previousMapping);
+        }
+      }
+    }
+  }
+
+  private static Set<Move> computeUnneededMoves(
+      BasicBlock block,
+      DebugLocalsChange postSpillLocalsChange,
+      LinearScanRegisterAllocator allocator) {
+    Set<Move> unneededMoves = Sets.newIdentityHashSet();
+    IntSet usedRegisters = new IntOpenHashSet();
+    IntSet clobberedRegisters = new IntOpenHashSet();
+    // Backwards instruction scan collecting the registers used by actual instructions.
+    boolean inEntrySpillMoves = false;
+    InstructionListIterator it = block.listIterator(block.getInstructions().size());
+    while (it.hasPrevious()) {
+      Instruction instruction = it.previous();
+      if (instruction == postSpillLocalsChange) {
+        inEntrySpillMoves = true;
+      }
+      // If this is a move in the block-entry spill moves check if it is unneeded.
+      if (inEntrySpillMoves && instruction.isMove()) {
+        Move move = instruction.asMove();
+        int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+        int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+        if (!usedRegisters.contains(dst) && !clobberedRegisters.contains(src)) {
+          unneededMoves.add(move);
+          continue;
+        }
+      }
+      if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
+        int register =
+            allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber());
+        // The register is defined anew, so uses before this are on distinct values.
+        usedRegisters.remove(register);
+        // Mark it clobbered to avoid any uses in locals after this point to become invalid.
+        clobberedRegisters.add(register);
+      }
+      if (!instruction.inValues().isEmpty()) {
+        for (Value inValue : instruction.inValues()) {
+          if (inValue.needsRegister()) {
+            int register = allocator.getRegisterForValue(inValue, instruction.getNumber());
+            // Record the register as being used.
+            usedRegisters.add(register);
+          }
+        }
+      }
+    }
+    return unneededMoves;
+  }
+
+  private static void updateDebugLocalsRegisterMap(
+      Int2IntMap mapping, Int2ReferenceMap<DebugLocalInfo> locals) {
+    // If nothing is mapped nothing needs to be changed.
+    if (mapping.isEmpty()) {
+      return;
+    }
+    // Locals is final, so we copy and clear it during update.
+    Int2ReferenceMap<DebugLocalInfo> copy = new Int2ReferenceOpenHashMap<>(locals);
+    locals.clear();
+    for (Entry<DebugLocalInfo> entry : copy.int2ReferenceEntrySet()) {
+      int oldRegister = entry.getIntKey();
+      int newRegister = mapping.getOrDefault(oldRegister, oldRegister);
+      locals.put(newRegister, entry.getValue());
+    }
+  }
+
   // Removes calls to Throwable.addSuppressed(Throwable) and rewrites
   // Throwable.getSuppressed() into new Throwable[0].
   //
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 7375303..cdf37c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -113,6 +113,9 @@
 
     eligibleClass =
         root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+    if (!eligibleClass.isClassType()) {
+      return false;
+    }
     eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
     if (eligibleClassDefinition == null && lambdaRewriter != null) {
       // Check if the class is synthesized for a desugared lambda
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
index 9bdd55e..6613534 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -36,14 +36,12 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod,
-      DexEncodedMethod context, Type type) {
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     if (methodMap.get(originalMethod) == newMethod) {
       assert type == Type.VIRTUAL || type == Type.DIRECT;
       return Type.STATIC;
     }
-    return super.mapInvocationType(newMethod, originalMethod, context, type);
+    return super.mapInvocationType(newMethod, originalMethod, type);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index b59e3e9..72fc987 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -128,7 +128,7 @@
         return false;
       }
       lenseBuilder.add(encodedMethod.method);
-      accessFlags.setFinal();
+      accessFlags.promoteToFinal();
       accessFlags.promoteToPublic();
       // Although the current method became public, it surely has the single virtual target.
       encodedMethod.method.setSingleVirtualMethodCache(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 98a0da4..5e19d48 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.optimize;
 
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
@@ -47,11 +46,8 @@
     return new Builder(appInfo);
   }
 
-
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 2db2dce..c685d84 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -15,6 +15,7 @@
 import java.util.Set;
 
 final class PublicizerLense extends NestedGraphLense {
+
   private final AppView appView;
   private final Set<DexMethod> publicizedMethods;
 
@@ -34,8 +35,7 @@
   }
 
   @Override
-  public GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type) {
+  public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
     GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
     method = previous.getMethod();
     type = previous.getType();
@@ -46,7 +46,7 @@
     return super.lookupMethod(method, context, type);
   }
 
-  private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexEncodedMethod context) {
+  private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexMethod context) {
     GraphLenseLookupResult lookup =
         appView.graphLense().lookupMethod(method, context, Type.VIRTUAL);
     DexMethod signatureInCurrentWorld = lookup.getMethod();
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index 10d2a31..96cb926 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -10,12 +10,13 @@
 @Keep
 public final class ArrayReference implements TypeReference {
 
-  private final int dimentions;
+  private final int dimensions;
   private final TypeReference baseType;
   private String descriptor;
 
-  private ArrayReference(int dimentions, TypeReference baseType, String descriptor) {
-    this.dimentions = dimentions;
+  private ArrayReference(int dimensions, TypeReference baseType, String descriptor) {
+    assert dimensions > 0;
+    this.dimensions = dimensions;
     this.baseType = baseType;
     this.descriptor = descriptor;
   }
@@ -23,15 +24,18 @@
   static ArrayReference fromDescriptor(String descriptor) {
     for (int i = 0; i < descriptor.length(); i++) {
       if (descriptor.charAt(i) != '[') {
-        return new ArrayReference(
-            i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+        if (i > 0) {
+          return new ArrayReference(
+              i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+        }
+        break;
       }
     }
     throw new Unreachable("Invalid array type descriptor: " + descriptor);
   }
 
-  public int getDimentions() {
-    return dimentions;
+  public int getDimensions() {
+    return dimensions;
   }
 
   public TypeReference getMemberType() {
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 adc6979..8560ba7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2414,6 +2414,10 @@
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
       assert method != null;
       assert refinedReceiverType.isSubtypeOf(method.holder, this);
+      if (method.holder.isArrayType()) {
+        assert method.name == dexItemFactory.cloneMethodName;
+        return null;
+      }
       DexClass holder = definitionFor(method.holder);
       if (holder == null || holder.isLibraryClass() || holder.isInterface()) {
         return null;
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 543dcc4..71b3d53 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
-import com.google.common.collect.ImmutableSet;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -61,35 +60,23 @@
     boolean value = false;
   }
 
-  public static boolean hasReferencesOutside(
-      AppInfoWithSubtyping appInfo, DexProgramClass clazz, Set<DexType> types) {
-    BooleanBox result = new BooleanBox();
-
-    new MainDexDirectReferenceTracer(appInfo, type -> {
-      if (!types.contains(type)) {
-        DexClass cls = appInfo.definitionFor(type);
-        if (cls != null && !cls.isLibraryClass()) {
-          result.value = true;
-        }
-      }
-    }).run(ImmutableSet.of(clazz.type));
-
-    return result.value;
-  }
-
   public static boolean hasReferencesOutsideFromCode(
       AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
 
     BooleanBox result = new BooleanBox();
 
-    new MainDexDirectReferenceTracer(appInfo, type -> {
-      if (!classes.contains(type)) {
-        DexClass cls = appInfo.definitionFor(type);
-        if (cls != null && !cls.isLibraryClass()) {
-          result.value = true;
-        }
-      }
-    }).runOnCode(method);
+    new MainDexDirectReferenceTracer(
+            appInfo,
+            type -> {
+              DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+              if (baseType.isClassType() && !classes.contains(baseType)) {
+                DexClass cls = appInfo.definitionFor(baseType);
+                if (cls != null && !cls.isLibraryClass()) {
+                  result.value = true;
+                }
+              }
+            })
+        .runOnCode(method);
 
     return result.value;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 898d06c..ff196e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -843,6 +843,9 @@
   }
 
   private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRule context) {
+    if (type.isVoidType()) {
+      return;
+    }
     if (type.isArrayType()) {
       type = type.toBaseType(application.dexItemFactory);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 6cf0da0..f502692 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -92,7 +92,7 @@
             // corresponding methods in this class. This might happen if we only keep this
             // class around for its constants.
             // For now, we remove the final flag to still be able to mark it abstract.
-            clazz.accessFlags.unsetFinal();
+            clazz.accessFlags.demoteFromFinal();
           }
           clazz.accessFlags.setAbstract();
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 45ca1af..956d6bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
@@ -53,6 +52,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -211,13 +211,13 @@
   private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
 
   // Map from source class to target class.
-  private final Map<DexType, DexType> mergedClasses = new HashMap<>();
+  private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
 
   // Map from target class to the super classes that have been merged into the target class.
-  private final Map<DexType, Set<DexType>> mergedClassesInverse = new HashMap<>();
+  private final Map<DexType, Set<DexType>> mergedClassesInverse = new IdentityHashMap<>();
 
   // Set of types that must not be merged into their subtype.
-  private final Set<DexType> pinnedTypes = new HashSet<>();
+  private final Set<DexType> pinnedTypes = Sets.newIdentityHashSet();
 
   // The resulting graph lense that should be used after class merging.
   private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
@@ -336,15 +336,16 @@
   }
 
   private void markTypeAsPinned(DexType type, AbortReason reason) {
-    if (appInfo.isPinned(type)) {
+    DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    if (!baseType.isClassType() || appInfo.isPinned(baseType)) {
       // We check for the case where the type is pinned according to appInfo.isPinned,
       // so we only need to add it here if it is not the case.
       return;
     }
 
-    DexClass clazz = appInfo.definitionFor(type);
+    DexClass clazz = appInfo.definitionFor(baseType);
     if (clazz != null && clazz.isProgramClass()) {
-      boolean changed = pinnedTypes.add(type);
+      boolean changed = pinnedTypes.add(baseType);
 
       if (Log.ENABLED) {
         if (changed && isMergeCandidate(clazz.asProgramClass(), ImmutableSet.of())) {
@@ -685,7 +686,7 @@
     if (Log.ENABLED) {
       Log.debug(getClass(), "Merged %d classes.", mergedClasses.size());
     }
-    return renamedMembersLense.build(graphLense, mergedClasses, synthesizedBridges, appInfo);
+    return renamedMembersLense.build(graphLense, mergedClasses, appInfo);
   }
 
   private boolean methodResolutionMayChange(DexClass source, DexClass target) {
@@ -742,10 +743,6 @@
     return false;
   }
 
-  private boolean hasReferencesOutside(DexProgramClass clazz, Set<DexType> types) {
-    return MainDexDirectReferenceTracer.hasReferencesOutside(appInfo, clazz, types);
-  }
-
   private void mergeClassIfPossible(DexProgramClass clazz) {
     if (!mergeCandidates.contains(clazz)) {
       return;
@@ -1214,7 +1211,6 @@
         // The bridge is now the public method serving the role of the original method, and should
         // reflect that this method was publicized.
         assert bridge.accessFlags.isPromotedToPublic();
-        method.accessFlags.unsetPromotedToPublic();
       }
       return bridge;
     }
@@ -1409,7 +1405,6 @@
 
   private static void makePrivate(DexEncodedMethod method) {
     assert !method.accessFlags.isAbstract();
-    method.accessFlags.unsetPromotedToPublic();
     method.accessFlags.unsetPublic();
     method.accessFlags.unsetProtected();
     method.accessFlags.setPrivate();
@@ -1432,42 +1427,19 @@
       for (DexProgramClass clazz : appInfo.classes()) {
         clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
         clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
-        clazz.setVirtualMethods(removeDupes(clazz.virtualMethods()));
         clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
         clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
       }
+      for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+        synthesizedBridge.updateMethodSignatures(this::fixupMethod);
+      }
       // Record type renamings so check-cast and instance-of checks are also fixed.
       for (DexType type : mergedClasses.keySet()) {
-        DexType fixed = fixupType(type);
-        lense.map(type, fixed);
+        lense.map(type, fixupType(type));
       }
       return lense.build(application.dexItemFactory, graphLense);
     }
 
-    private DexEncodedMethod[] removeDupes(DexEncodedMethod[] methods) {
-      if (methods == null) {
-        return null;
-      }
-      Map<DexMethod, DexEncodedMethod> filtered = new IdentityHashMap<>();
-      for (DexEncodedMethod method : methods) {
-        DexEncodedMethod previous = filtered.put(method.method, method);
-        if (previous != null) {
-          if (!previous.accessFlags.isBridge()) {
-            if (!method.accessFlags.isBridge()) {
-              throw new CompilationError(
-                  "Class merging produced invalid result on: " + previous.toSourceString());
-            } else {
-              filtered.put(previous.method, previous);
-            }
-          }
-        }
-      }
-      if (filtered.size() == methods.length) {
-        return methods;
-      }
-      return filtered.values().toArray(DexEncodedMethod.EMPTY_ARRAY);
-    }
-
     private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
       if (methods == null) {
         return null;
@@ -1475,12 +1447,9 @@
       for (int i = 0; i < methods.length; i++) {
         DexEncodedMethod encodedMethod = methods[i];
         DexMethod method = encodedMethod.method;
-        DexProto newProto = getUpdatedProto(method.proto);
-        DexType newHolder = fixupType(method.holder);
-        DexMethod newMethod = application.dexItemFactory.createMethod(newHolder, newProto,
-            method.name);
-        if (newMethod != encodedMethod.method) {
-          lense.move(encodedMethod.method, newMethod);
+        DexMethod newMethod = fixupMethod(method);
+        if (newMethod != method) {
+          lense.move(method, newMethod);
           methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
         }
       }
@@ -1505,7 +1474,12 @@
       return fields;
     }
 
-    private DexProto getUpdatedProto(DexProto proto) {
+    private DexMethod fixupMethod(DexMethod method) {
+      return application.dexItemFactory.createMethod(
+          fixupType(method.holder), fixupProto(method.proto), method.name);
+    }
+
+    private DexProto fixupProto(DexProto proto) {
       DexProto result = protoFixupCache.get(proto);
       if (result == null) {
         DexType returnType = fixupType(proto.returnType);
@@ -1522,12 +1496,13 @@
         DexType fixed = fixupType(base);
         if (base == fixed) {
           return type;
-        } else {
-          return type.replaceBaseType(fixed, application.dexItemFactory);
         }
+        return type.replaceBaseType(fixed, application.dexItemFactory);
       }
-      while (mergedClasses.containsKey(type)) {
-        type = mergedClasses.get(type);
+      if (type.isClassType()) {
+        while (mergedClasses.containsKey(type)) {
+          type = mergedClasses.get(type);
+        }
       }
       return type;
     }
@@ -1680,6 +1655,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      throw new Unreachable();
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       throw new Unreachable();
     }
@@ -1705,8 +1685,7 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       // First look up the method using the existing graph lense (for example, the type will have
       // changed if the method was publicized by ClassAndMemberPublicizer).
       GraphLenseLookupResult lookup = graphLense.lookupMethod(method, context, type);
@@ -1750,7 +1729,7 @@
   public static class IllegalAccessDetector extends UseRegistry {
 
     private boolean foundIllegalAccess = false;
-    private DexEncodedMethod context = null;
+    private DexMethod context = null;
 
     private final AppView<? extends AppInfo> appView;
     private final DexClass source;
@@ -1766,7 +1745,7 @@
     }
 
     public void setContext(DexEncodedMethod context) {
-      this.context = context;
+      this.context = context.method;
     }
 
     private boolean checkFieldReference(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 20a05ab..6576433 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -14,17 +13,14 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.shaking.VerticalClassMerger.SynthesizedBridgeCode;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 
 // This graph lense is instantiated during vertical class merging. The graph lense is context
 // sensitive in the enclosing class of a given invoke *and* the type of the invoke (e.g., invoke-
@@ -55,7 +51,7 @@
 
   private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
       contextualVirtualToDirectMethodMaps;
-  private final Set<DexMethod> mergedMethods;
+  private Set<DexMethod> mergedMethods;
   private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
   public VerticalClassMergerGraphLense(
@@ -83,19 +79,29 @@
   }
 
   @Override
+  public DexType getOriginalType(DexType type) {
+    return previousLense.getOriginalType(type);
+  }
+
+  @Override
   public DexMethod getOriginalMethodSignature(DexMethod method) {
     return super.getOriginalMethodSignature(
         originalMethodSignaturesForBridges.getOrDefault(method, method));
   }
 
   @Override
-  public GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type) {
+  public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
     assert isContextFreeForMethod(method) || (context != null && type != null);
-    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
-    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
+    DexMethod previousContext =
+        originalMethodSignaturesForBridges.containsKey(context)
+            ? originalMethodSignaturesForBridges.get(context)
+            : originalMethodSignatures != null
+                ? originalMethodSignatures.getOrDefault(context, context)
+                : context;
+    GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
+    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context)) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
-          contextualVirtualToDirectMethodMaps.get(context.method.holder);
+          contextualVirtualToDirectMethodMaps.get(context.holder);
       if (virtualToDirectMethodMap != null) {
         GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
         if (lookup != null) {
@@ -113,10 +119,8 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
   }
 
   @Override
@@ -170,7 +174,6 @@
     public GraphLense build(
         GraphLense previousLense,
         Map<DexType, DexType> mergedClasses,
-        List<SynthesizedBridgeCode> synthesizedBridges,
         AppInfo appInfo) {
       if (fieldMap.isEmpty()
           && methodMap.isEmpty()
@@ -179,14 +182,6 @@
       }
       Map<DexProto, DexProto> cache = new HashMap<>();
       BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
-      // Update all synthesized bridges.
-      Function<DexMethod, DexMethod> synthesizedBridgeTransformer =
-          method ->
-              getMethodSignatureAfterClassMerging(
-                  method, mergedClasses, appInfo.dexItemFactory, cache);
-      for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
-        synthesizedBridge.updateMethodSignatures(synthesizedBridgeTransformer);
-      }
       // Build new graph lense.
       return new VerticalClassMergerGraphLense(
           appInfo,
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index ec12f82..c9be1b8 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -150,16 +151,17 @@
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
       // Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
+      DexType originalType = graphLense.getOriginalType(clazz.type);
       DexString renamedClassName = namingLens.lookupDescriptor(clazz.getType());
       Supplier<ClassNaming.Builder> onDemandClassNamingBuilder =
           Suppliers.memoize(
               () ->
                   classNameMapperBuilder.classNamingBuilder(
                       DescriptorUtils.descriptorToJavaType(renamedClassName.toString()),
-                      clazz.toString()));
+                      originalType.toSourceString()));
 
       // If the class is renamed add it to the classNamingBuilder.
-      addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
+      addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
 
       // First transfer renamed fields to classNamingBuilder.
       addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
@@ -306,10 +308,12 @@
   }
 
   @SuppressWarnings("ReturnValueIgnored")
-  private static void addClassToClassNaming(DexProgramClass clazz, DexString renamedClassName,
+  private static void addClassToClassNaming(
+      DexType originalType,
+      DexString renamedClassName,
       Supplier<Builder> onDemandClassNamingBuilder) {
     // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
-    if (!clazz.toString().equals(renamedClassName.toString())) {
+    if (originalType.descriptor != renamedClassName) {
       // Not using return value, it's registered in classNameMapperBuilder
       onDemandClassNamingBuilder.get();
     }
diff --git a/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
new file mode 100644
index 0000000..4a8b5be
--- /dev/null
+++ b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
@@ -0,0 +1,47 @@
+// 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 classmerging;
+
+import java.lang.reflect.Method;
+
+public class PinnedArrayParameterTypesTest {
+
+  public static void main(String[] args) throws Exception {
+    for (Method method : TestClass.class.getMethods()) {
+      if (method.getName().equals("method")) {
+        Class<?> parameterType = method.getParameterTypes()[0];
+
+        // Should print classmerging.PinnedArrayParameterTypesTest$Interface when
+        // -keepparameternames is used.
+        System.out.println(parameterType.getName());
+
+        method.invoke(null, new Object[]{ new InterfaceImpl[]{ new InterfaceImpl() } });
+        break;
+      }
+    }
+  }
+
+  public interface Interface {
+
+    void foo();
+  }
+
+  public static class InterfaceImpl implements Interface {
+
+    @Override
+    public void foo() {
+      System.out.println("In InterfaceImpl.foo()");
+    }
+  }
+
+  public static class TestClass {
+
+    // This method has been kept explicitly by a keep rule. Therefore, since -keepparameternames is
+    // used, Interface must not be merged into InterfaceImpl.
+    public static void method(Interface[] obj) {
+      obj[0].foo();
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index e3e0006..c75058d 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -43,6 +43,12 @@
 -keep public class classmerging.PinnedParameterTypesTest$TestClass {
   public static void method(...);
 }
+-keep public class classmerging.PinnedArrayParameterTypesTest {
+  public static void main(...);
+}
+-keep public class classmerging.PinnedArrayParameterTypesTest$TestClass {
+  public static void method(...);
+}
 -keep public class classmerging.ProguardFieldMapTest {
   public static void main(...);
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 88d077b..37e4dec 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -3,13 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -17,6 +22,9 @@
     extends TestCompilerBuilder<
         D8Command, Builder, D8TestCompileResult, D8TestRunResult, D8TestBuilder> {
 
+  // Consider an in-order collection of both class and files on the classpath.
+  private List<Class<?>> classpathClasses = new ArrayList<>();
+
   private D8TestBuilder(TestState state, Builder builder) {
     super(state, builder, Backend.DEX);
   }
@@ -34,6 +42,29 @@
   D8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
+    if (!classpathClasses.isEmpty()) {
+      Path cp;
+      try {
+        cp = getState().getNewTempFolder().resolve("cp.jar");
+      } catch (IOException e) {
+        throw builder.getReporter().fatalError("Failed to create temp file for classpath archive");
+      }
+      ArchiveConsumer archiveConsumer = new ArchiveConsumer(cp);
+      for (Class<?> classpathClass : classpathClasses) {
+        try {
+          archiveConsumer.accept(
+              ByteDataView.of(ToolHelper.getClassAsBytes(classpathClass)),
+              DescriptorUtils.javaTypeToDescriptor(classpathClass.getTypeName()),
+              builder.getReporter());
+        } catch (IOException e) {
+          builder
+              .getReporter()
+              .error("Failed to read bytes for classpath class: " + classpathClass.getTypeName());
+        }
+      }
+      archiveConsumer.finished(builder.getReporter());
+      builder.addClasspathFiles(cp);
+    }
     ToolHelper.runD8(builder, optionsConsumer);
     return new D8TestCompileResult(getState(), app.get());
   }
@@ -43,7 +74,8 @@
   }
 
   public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
-    return addClasspathFiles(getFilesForClasses(classes));
+    classpathClasses.addAll(classes);
+    return self();
   }
 
   public D8TestBuilder addClasspathFiles(Path... files) {
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index c4425b1..1a4e0ec 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
 
 public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
 
@@ -17,4 +21,15 @@
   protected Dex2OatTestRunResult self() {
     return this;
   }
+
+  public Dex2OatTestRunResult assertNoVerificationErrors() {
+    assertSuccess();
+    Matcher<? super String> matcher =
+        CoreMatchers.not(CoreMatchers.containsString("Verification error"));
+    assertThat(
+        errorMessage("Run dex2oat produced verification errors.", matcher.toString()),
+        getStdErr(),
+        matcher);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
new file mode 100644
index 0000000..b43bb8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
@@ -0,0 +1,43 @@
+// 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;
+
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+class Tester {
+  public int foo() {
+    float[][][] fs = new float[1][2][3];
+    return fs.length;
+  }
+
+  public static void main(String[] args) {
+    System.out.println(new Tester().foo());
+  }
+}
+
+// The DirectoryClasspathProvider asserts lookups are reference types which witnessed the issue.
+public class RegressionForPrimitiveDefinitionForLookup extends TestBase {
+
+  public final Class<Tester> CLASS = Tester.class;
+  public String EXPECTED = StringUtils.lines("1");
+
+  @Test
+  public void testWithArchiveClasspath() throws Exception {
+    testForD8()
+        .addClasspathClasses(CLASS)
+        .addProgramClasses(CLASS)
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testWithDirectoryClasspath() throws Exception {
+    testForD8()
+        .addClasspathFiles(ToolHelper.getClassPathForTests())
+        .addProgramClasses(CLASS)
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index a032fd6..89d0fff 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -108,11 +108,11 @@
     return builder.toString();
   }
 
-  private String errorMessage(String message) {
+  String errorMessage(String message) {
     return errorMessage(message, null);
   }
 
-  private String errorMessage(String message, String expected) {
+  String errorMessage(String message, String expected) {
     StringBuilder builder = new StringBuilder(message).append('\n');
     if (expected != null) {
       if (expected.contains(System.lineSeparator())) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 2284fa5..1836a29 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -86,16 +86,13 @@
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException, CompilationFailedException {
-    ToolHelper.runR8(
-        R8Command.builder()
-            .setOutput(Paths.get(temp.getRoot().getCanonicalPath()), OutputMode.DexIndexed)
+    inspector =
+        testForR8(Backend.DEX)
             .addProgramFiles(EXAMPLE_JAR)
-            .addProguardConfigurationFiles(proguardConfig)
-            .setDisableMinification(true)
-            .build(),
-        optionsConsumer);
-    inspector = new CodeInspector(
-        Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
+            .addKeepRuleFiles(proguardConfig)
+            .addOptionsModification(optionsConsumer)
+            .compile()
+            .inspector();
   }
 
   private CodeInspector inspector;
@@ -466,6 +463,29 @@
   }
 
   @Test
+  public void testPinnedArrayParameterTypes() throws Throwable {
+    String main = "classmerging.PinnedArrayParameterTypesTest";
+    Path[] programFiles =
+        new Path[] {
+            CF_DIR.resolve("PinnedArrayParameterTypesTest.class"),
+            CF_DIR.resolve("PinnedArrayParameterTypesTest$Interface.class"),
+            CF_DIR.resolve("PinnedArrayParameterTypesTest$InterfaceImpl.class"),
+            CF_DIR.resolve("PinnedArrayParameterTypesTest$TestClass.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.PinnedArrayParameterTypesTest",
+            "classmerging.PinnedArrayParameterTypesTest$Interface",
+            "classmerging.PinnedArrayParameterTypesTest$InterfaceImpl",
+            "classmerging.PinnedArrayParameterTypesTest$TestClass");
+    runTest(
+        main,
+        programFiles,
+        preservedClassNames::contains,
+        getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"));
+  }
+
+  @Test
   public void testProguardFieldMap() throws Throwable {
     String main = "classmerging.ProguardFieldMapTest";
     Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
index e9bf581..378cf0e 100644
--- a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
@@ -27,14 +27,11 @@
   private void test(Path mapping) throws Exception {
     testForR8(Backend.DEX)
         .addInnerClasses(PrintMappingTest.class)
-        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keep,allowobfuscation class " + TestClass.class.getTypeName())
         .addKeepRules("-printmapping " + mapping)
         .compile();
     assertTrue(mapping.toFile().exists());
   }
 
-  static class TestClass {
-
-    public static void main(String[] args) {}
-  }
+  static class TestClass {}
 }
diff --git a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
index 1c28054..5b7a11e 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -65,10 +66,15 @@
 
   // Return the package name in the app for this package.
   private String pathForThisPackage(AndroidApp app) throws Exception {
-    ClassNameMapper mapper =
-        ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
-    String x = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
-    return x.substring(0, x.lastIndexOf('.')).replace('.', '/');
+    String name;
+    if (app.getProguardMapOutputData() != null) {
+      ClassNameMapper mapper =
+          ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
+      name = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
+    } else {
+      name = Main.class.getTypeName();
+    }
+    return name.substring(0, name.lastIndexOf('.')).replace('.', '/');
   }
 
   private void checkResourceNames(Set<String> resourceNames, String expectedPackageName) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b302615..0b246a5 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -260,9 +260,8 @@
     }
 
     if (inspection != null) {
-      CodeInspector inspector = new CodeInspector(out,
-          minify.isMinify() ? proguardMap.toString()
-              : null);
+      CodeInspector inspector =
+          new CodeInspector(out, minify.isMinify() ? proguardMap.toString() : null);
       inspection.accept(inspector);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index f65c1d4..3833645 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java
deleted file mode 100644
index cf6d7c0..0000000
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.shaking.ifrule.accessrelaxation;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class IfRuleWithAccessRelaxation extends TestBase {
-
-  private final Backend backend;
-
-  public IfRuleWithAccessRelaxation(Backend backend) {
-    this.backend = backend;
-  }
-
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return Backend.values();
-  }
-
-  @Test
-  public void r8Test() throws Exception {
-    CodeInspector inspector =
-        testForR8(backend)
-            .addInnerClasses(IfRuleWithAccessRelaxation.class)
-            .addKeepRules(
-                "-keep class " + TestClass.class.getTypeName() + " { int field; void method(); }",
-                "-if class " + TestClass.class.getTypeName() + " { protected int field; }",
-                "-keep class " + Unused1.class.getTypeName(),
-                "-if class " + TestClass.class.getTypeName() + " { protected void method(); }",
-                "-keep class " + Unused2.class.getTypeName(),
-                "-allowaccessmodification")
-            .compile()
-            .inspector();
-
-    assertTrue(inspector.clazz(TestClass.class).isPublic());
-    assertTrue(inspector.clazz(TestClass.class).uniqueMethodWithName("method").isPublic());
-    assertTrue(inspector.clazz(TestClass.class).uniqueFieldWithName("field").isPublic());
-
-    assertThat(inspector.clazz(Unused1.class), isPresent());
-    assertThat(inspector.clazz(Unused2.class), isPresent());
-  }
-
-  protected static class TestClass {
-
-    protected int field = 42;
-
-    protected void method() {}
-  }
-
-  static class Unused1 {}
-
-  static class Unused2 {}
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
new file mode 100644
index 0000000..560ebd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfRuleWithAccessRelaxationTest extends TestBase {
+
+  private final Backend backend;
+
+  public IfRuleWithAccessRelaxationTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  @Test
+  public void r8Test() throws Exception {
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(IfRuleWithAccessRelaxationTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                "-keep class " + TestClass.class.getTypeName() + " { int field; }",
+                "-if class " + TestClass.class.getTypeName() + " { protected int field; }",
+                "-keep class " + Unused1.class.getTypeName(),
+                "-if class " + TestClass.class.getTypeName() + " {",
+                "  private !final void privateMethod();",
+                "}",
+                "-keep class " + Unused2.class.getTypeName(),
+                "-if class " + TestClass.class.getTypeName() + " {",
+                "  protected void virtualMethod();",
+                "}",
+                "-keep class " + Unused3.class.getTypeName(),
+                "-allowaccessmodification")
+            .enableInliningAnnotations()
+            .compile()
+            .inspector();
+
+    assertTrue(inspector.clazz(TestClass.class).isPublic());
+    assertThat(inspector.clazz(TestClass.class).uniqueFieldWithName("field"), isPublic());
+    assertThat(
+        inspector.clazz(TestClass.class).uniqueMethodWithName("privateMethod"),
+        allOf(isPublic(), isFinal()));
+    assertThat(inspector.clazz(TestClass.class).uniqueMethodWithName("virtualMethod"), isPublic());
+
+    assertThat(inspector.clazz(Unused1.class), isPresent());
+    assertThat(inspector.clazz(Unused2.class), isPresent());
+    assertThat(inspector.clazz(Unused3.class), isPresent());
+  }
+
+  protected static class TestClass {
+
+    public static void main(String[] args) {
+      TestClass obj = new TestClass();
+      obj.privateMethod();
+      obj.virtualMethod();
+    }
+
+    protected int field = 42;
+
+    @NeverInline
+    private void privateMethod() {
+      System.out.println("In privateMethod()");
+    }
+
+    @NeverInline
+    protected void virtualMethod() {
+      System.out.println("In virtualMethod()");
+    }
+  }
+
+  static class Unused1 {}
+
+  static class Unused2 {}
+
+  static class Unused3 {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
new file mode 100644
index 0000000..a12620d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
@@ -0,0 +1,113 @@
+// 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.shaking.ifrule.classstaticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfRuleWithClassStaticizerTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public IfRuleWithClassStaticizerTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("In method()");
+
+    if (backend == Backend.CF) {
+      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    }
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(IfRuleWithClassStaticizerTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                "-if class " + StaticizerCandidate.Companion.class.getTypeName() + " {",
+                "  public !static void method();",
+                "}",
+                "-keep class " + Unused.class.getTypeName())
+            .enableInliningAnnotations()
+            .enableClassInliningAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classSubject = inspector.clazz(StaticizerCandidate.class);
+    assertThat(classSubject, isPresent());
+
+    if (backend == Backend.CF) {
+      // The class staticizer is not enabled for CF.
+      assertThat(inspector.clazz(Unused.class), isPresent());
+    } else {
+      assert backend == Backend.DEX;
+
+      // There should be a static method on StaticizerCandidate after staticizing.
+      List<FoundMethodSubject> staticMethods =
+          classSubject.allMethods().stream()
+              .filter(method -> method.isStatic() && !method.isClassInitializer())
+              .collect(Collectors.toList());
+      assertEquals(1, staticMethods.size());
+      assertEquals(
+          "void " + StaticizerCandidate.Companion.class.getTypeName() + ".method()",
+          staticMethods.get(0).getOriginalSignature().toString());
+
+      // The Companion class should not be present after staticizing.
+      assertThat(inspector.clazz(StaticizerCandidate.Companion.class), not(isPresent()));
+
+      // TODO(b/122867080): The Unused class should be present due to the -if rule.
+      assertThat(inspector.clazz(Unused.class), not(isPresent()));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      StaticizerCandidate.companion.method();
+    }
+  }
+
+  @NeverClassInline
+  static class StaticizerCandidate {
+
+    static final Companion companion = new Companion();
+
+    @NeverClassInline
+    static class Companion {
+
+      @NeverInline
+      public void method() {
+        System.out.println("In method()");
+      }
+    }
+  }
+
+  static class Unused {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
new file mode 100644
index 0000000..d86bc68
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
@@ -0,0 +1,105 @@
+// 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.shaking.ifrule.interfacemethoddesugaring;
+
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+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.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class IfRuleWithInterfaceMethodDesugaringTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines("In Interface.staticMethod()", "In Interface.virtualMethod()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(IfRuleWithInterfaceMethodDesugaringTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                "-if class " + Interface.class.getTypeName() + " {",
+                "  !public static void staticMethod();",
+                "}",
+                "-keep class " + Unused1.class.getTypeName(),
+                "-if class " + Interface.class.getTypeName() + " {",
+                "  !public !static void virtualMethod();",
+                "}",
+                "-keep class " + Unused2.class.getTypeName())
+            .enableInliningAnnotations()
+            .enableClassInliningAnnotations()
+            .enableMergeAnnotations()
+            .setMinApi(AndroidApiLevel.M)
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classSubject =
+        inspector.clazz(Interface.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
+    assertThat(classSubject, isPresent());
+    assertEquals(2, classSubject.allMethods().size());
+
+    MethodSubject staticMethodSubject = classSubject.uniqueMethodWithName("staticMethod");
+    assertThat(staticMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+    // TODO(b/122867087): Should not be necessary to use `DEFAULT_METHOD_PREFIX`.
+    MethodSubject virtualMethodSubject =
+        classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "virtualMethod");
+    assertThat(virtualMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+    // TODO(b/122875545): The Unused class should be present due to the -if rule.
+    assertThat(inspector.clazz(Unused1.class), not(isPresent()));
+    assertThat(inspector.clazz(Unused2.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Interface.staticMethod();
+      new InterfaceImpl().virtualMethod();
+    }
+  }
+
+  @NeverClassInline
+  @NeverMerge
+  interface Interface {
+
+    @NeverInline
+    static void staticMethod() {
+      System.out.println("In Interface.staticMethod()");
+    }
+
+    @NeverInline
+    default void virtualMethod() {
+      System.out.println("In Interface.virtualMethod()");
+    }
+  }
+
+  @NeverClassInline
+  static class InterfaceImpl implements Interface {}
+
+  static class Unused1 {}
+
+  static class Unused2 {}
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 9983320..a9058ff 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -45,6 +45,7 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -82,13 +83,14 @@
   public CodeInspector(
       List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException {
-    if (mappingFile != null) {
-      this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
-      BiMapContainer<String, String> nameMapping = this.mapping.getObfuscatedToOriginalMapping();
+    Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
+    if (mappingPath != null && Files.exists(mappingPath)) {
+      mapping = ClassNameMapper.mapperFromFile(mappingPath);
+      BiMapContainer<String, String> nameMapping = mapping.getObfuscatedToOriginalMapping();
       obfuscatedToOriginalMapping = nameMapping.original;
       originalToObfuscatedMapping = nameMapping.inverse;
     } else {
-      this.mapping = null;
+      mapping = null;
       originalToObfuscatedMapping = null;
       obfuscatedToOriginalMapping = null;
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 8cd5cc7..11ee3af 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -102,7 +102,7 @@
   public MethodSubject uniqueMethodWithName(String name) {
     MethodSubject methodSubject = null;
     for (FoundMethodSubject candidate : allMethods()) {
-      if (candidate.getOriginalName().equals(name)) {
+      if (candidate.getOriginalName(false).equals(name)) {
         assert methodSubject == null;
         methodSubject = candidate;
       }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index b2dff57..103ffb9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -146,6 +146,28 @@
     };
   }
 
+  public static Matcher<MemberSubject> isStatic() {
+    return new TypeSafeMatcher<MemberSubject>() {
+      @Override
+      public boolean matchesSafely(final MemberSubject subject) {
+        return subject.isPresent() && subject.isStatic();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText(" present");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MemberSubject subject, Description description) {
+        description
+            .appendText(type(subject) + " ")
+            .appendValue(name(subject))
+            .appendText(" was not");
+      }
+    };
+  }
+
   public static Matcher<Subject> isSynthetic() {
     return new TypeSafeMatcher<Subject>() {
       @Override
@@ -227,6 +249,28 @@
     };
   }
 
+  public static Matcher<MethodSubject> isFinal() {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      public boolean matchesSafely(final MethodSubject method) {
+        return method.isPresent() && method.isFinal();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText("is final");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject method, Description description) {
+        description
+            .appendText("method ")
+            .appendValue(method.getOriginalName())
+            .appendText(" was not");
+      }
+    };
+  }
+
   public static <T extends MemberSubject> Matcher<T> isPrivate() {
     return hasVisibility(Visibility.PRIVATE);
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index a95b6e6..af1fe14 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -25,8 +25,22 @@
   public abstract Signature getFinalSignature();
 
   public String getOriginalName() {
+    return getOriginalName(true);
+  }
+
+  public String getOriginalName(boolean qualified) {
     Signature originalSignature = getOriginalSignature();
-    return originalSignature == null ? null : originalSignature.name;
+    if (originalSignature != null) {
+      String name = originalSignature.name;
+      if (!qualified) {
+        int index = name.lastIndexOf(".");
+        if (index >= 0) {
+          return name.substring(index + 1);
+        }
+      }
+      return name;
+    }
+    return null;
   }
 
   public String getFinalName() {
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 770a651..5dc42ec 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -79,6 +79,14 @@
       'flavor': 'standard',
       'releaseTarget': 'app:assembleRelease',
   },
+  'tivi': {
+      'app_id': 'app.tivi',
+      # Forked from https://github.com/chrisbanes/tivi.git removing
+      # signingConfigs.
+      'git_repo': 'https://github.com/sgjesse/tivi.git',
+      # TODO(123047413): Fails with R8.
+      'skip': True,
+  },
   # This does not build yet.
   'muzei': {
       'git_repo': 'https://github.com/sgjesse/muzei.git',
@@ -267,9 +275,6 @@
 
       result_per_shrinker[shrinker] = result
 
-    if IsTrackedByGit('gradle.properties'):
-      GitCheckout('gradle.properties')
-
   return result_per_shrinker
 
 def BuildAppWithShrinker(app, config, shrinker, checkout_dir, options):
@@ -286,19 +291,6 @@
   archives_base_name = config.get(' archives_base_name', app_module)
   flavor = config.get('flavor')
 
-  # Ensure that gradle.properties is not modified before modifying it to
-  # select shrinker.
-  if IsTrackedByGit('gradle.properties'):
-    GitCheckout('gradle.properties')
-  with open("gradle.properties", "a") as gradle_properties:
-    if 'r8' in shrinker:
-      gradle_properties.write('\nandroid.enableR8=true\n')
-      if shrinker == 'r8full' or shrinker == 'r8full-minified':
-        gradle_properties.write('android.enableR8.fullMode=true\n')
-    else:
-      assert shrinker == 'proguard'
-      gradle_properties.write('\nandroid.enableR8=false\n')
-
   out = os.path.join(checkout_dir, 'out', shrinker)
   if not os.path.exists(out):
     os.makedirs(out)
@@ -317,8 +309,17 @@
     releaseTarget = app_module + ':' + 'assemble' + (
         flavor.capitalize() if flavor else '') + 'Release'
 
-  cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget, '--profile',
-      '--stacktrace']
+  # Value for property android.enableR8.
+  enableR8 = 'r8' in shrinker
+  # Value for property android.enableR8.fullMode.
+  enableR8FullMode = shrinker == 'r8full' or shrinker == 'r8full-minified'
+  # Build gradlew command line.
+  cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget,
+         '--profile', '--stacktrace',
+         '-Pandroid.enableR8=' + str(enableR8).lower(),
+         '-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+  if options.gradle_flags:
+    cmd.extend(options.gradle_flags.split(' '))
 
   utils.PrintCmd(cmd)
   build_process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
@@ -377,7 +378,7 @@
 def RebuildAppWithShrinker(apk, apk_dest, proguard_config_file, shrinker):
   assert 'r8' in shrinker
   assert apk_dest.endswith('.apk')
-  
+
   # Compile given APK with shrinker to temporary zip file.
   api = 28 # TODO(christofferqa): Should be the one from build.gradle
   android_jar = os.path.join(utils.REPO_ROOT, utils.ANDROID_JAR.format(api=api))
@@ -523,6 +524,8 @@
                     help='Run without building ToT first (only when using ToT)',
                     default=False,
                     action='store_true')
+  result.add_option('--gradle-flags', '--gradle_flags',
+                    help='Flags to pass in to gradle')
   (options, args) = result.parse_args(argv)
   if options.disable_tot:
     # r8.jar is required for recompiling the generated APK