Merge "Add tests for name clash while applying mappings."
diff --git a/build.gradle b/build.gradle
index 33d967f..5606a9e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -567,6 +567,7 @@
     from repackageSources.outputs.files
     from repackageDeps.outputs.files
     configureRelocations(it)
+    exclude "META-INF/*.kotlin_module"
 }
 
 task r8WithoutDeps(type: ShadowJar) {
@@ -596,6 +597,7 @@
     } else {
         from sourceSets.main.output
     }
+    exclude "META-INF/*.kotlin_module"
 }
 
 task R8NoManifestNoDeps(type: ShadowJar) {
@@ -619,6 +621,7 @@
     } else {
         from sourceSets.main.output
     }
+    exclude "META-INF/*.kotlin_module"
 }
 
 task D8(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 3521429..364a794 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -161,7 +161,7 @@
             String className = resource.getClassDescriptors().iterator().next();
             String entryName = getClassFileName(className);
             byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
-            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
+            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 7b17ec8..c5f0a40 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutionException;
@@ -88,13 +90,14 @@
         "Usage: disasm [options] <input-files>\n"
             + " where <input-files> are dex files\n"
             + " and options are:\n"
-            + "  --all                   # Include all information in disassembly.\n"
-            + "  --smali                 # Disassemble using smali syntax.\n"
-            + "  --ir                    # Print IR before and after optimization.\n"
-            + "  --pg-map <file>         # Proguard map <file> for mapping names.\n"
-            + "  --output                # Specify a file or directory to write to.\n"
-            + "  --version               # Print the version of r8.\n"
-            + "  --help                  # Print this message.";
+            + "  --all                       # Include all information in disassembly.\n"
+            + "  --smali                     # Disassemble using smali syntax.\n"
+            + "  --ir                        # Print IR before and after optimization.\n"
+            + "  --pg-map <file>             # Proguard map <file> for mapping names.\n"
+            + "  --pg-map-charset <charset>  # Charset for Proguard map file.\n"
+            + "  --output                    # Specify a file or directory to write to.\n"
+            + "  --version                   # Print the version of r8.\n"
+            + "  --help                      # Print this message.";
 
     private final boolean allInfo;
     private final boolean useSmali;
@@ -127,6 +130,18 @@
           builder.setUseIr(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
+        } else if (arg.equals("--pg-map-charset")) {
+          String charset = args[++i];
+          try {
+            Charset.forName(charset);
+          } catch (UnsupportedCharsetException e) {
+            builder.getReporter().error(
+                new StringDiagnostic(
+                    "Unsupported charset: " + charset + "." + System.lineSeparator()
+                        + "Supported charsets are: "
+                        + String.join(", ", Charset.availableCharsets().keySet()),
+                CommandLineOrigin.INSTANCE));
+          }
         } else if (arg.equals("--output")) {
           String outputPath = args[++i];
           builder.setOutputPath(Paths.get(outputPath));
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index fd85c25..c9f2032 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -70,6 +70,12 @@
     return extractMarker(app);
   }
 
+  public static Collection<Marker> extractMarkerFromClassProgramData(byte[] data)
+      throws IOException, ExecutionException {
+    AndroidApp app = AndroidApp.builder().addClassProgramData(data, Origin.unknown()).build();
+    return extractMarker(app);
+  }
+
   private static void addDexResources(AndroidApp.Builder appBuilder, Path file)
       throws IOException, ResourceException {
     if (FileUtils.isVDexFile(file)) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 30b57da..75b0c81 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.AppliedGraphLens;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -194,6 +195,7 @@
         new CfApplicationWriter(
                 application,
                 options,
+                marker,
                 deadCode,
                 graphLense,
                 namingLens,
@@ -485,6 +487,13 @@
         timing.end();
       }
 
+      // At this point all code has been mapped according to the graph lens. We cannot remove the
+      // graph lens entirely, though, since it is needed for mapping all field and method signatures
+      // back to the original program.
+      timing.begin("AppliedGraphLens construction");
+      appView.setGraphLense(new AppliedGraphLens(appView, application.classes()));
+      timing.end();
+
       if (options.printCfg) {
         if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
           System.out.print(printer.toString());
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 83d115d..4d27b8a 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.5.1-dev";
+  public static final String LABEL = "1.5.3-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 6bebb0d..8c79ea1 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -243,7 +243,7 @@
             stackValue, constant.asConstString().getValue(), ThrowingInfo.NO_THROW);
       } else if (constant.isDexItemBasedConstString()) {
         return new DexItemBasedConstString(
-            stackValue, constant.asDexItemBasedConstString().getItem());
+            stackValue, constant.asDexItemBasedConstString().getItem(), ThrowingInfo.NO_THROW);
       } else if (constant.isConstClass()) {
         return new ConstClass(stackValue, constant.asConstClass().getValue());
       } else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index c38cf60..0467fcd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -49,7 +49,7 @@
 
   @Override
   public boolean canThrow() {
-    // The ldc instruction may throw in Java bytecode.
+    // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index f0c9636..8ba58b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -57,7 +57,7 @@
 
   @Override
   public boolean canThrow() {
-    // The ldc instruction may throw in Java bytecode.
+    // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
     return 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/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
new file mode 100644
index 0000000..d2387f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -0,0 +1,138 @@
+// 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.graph;
+
+import com.android.tools.r8.ir.code.Invoke;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A graph lens that will not lead to any code rewritings in the {@link
+ * com.android.tools.r8.ir.conversion.LensCodeRewriter}, or parameter removals in the {@link
+ * com.android.tools.r8.ir.conversion.IRBuilder}.
+ *
+ * <p>The mappings from the original program to the generated program are kept, though.
+ */
+public class AppliedGraphLens extends GraphLense {
+
+  private final AppView<? extends AppInfo> appView;
+
+  private final BiMap<DexType, DexType> originalTypeNames = HashBiMap.create();
+  private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
+  private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+  private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
+      new IdentityHashMap<>();
+
+  public AppliedGraphLens(
+      AppView<? extends AppInfoWithSubtyping> appView, List<DexProgramClass> classes) {
+    this.appView = appView;
+
+    for (DexProgramClass clazz : classes) {
+      // Record original type names.
+      {
+        DexType type = clazz.type;
+        if (appView.verticallyMergedClasses() != null
+            && !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
+          DexType original = appView.graphLense().getOriginalType(type);
+          if (original != type) {
+            DexType existing = originalTypeNames.forcePut(type, original);
+            assert existing == null;
+          }
+        }
+      }
+
+      // Record original field signatures.
+      for (DexEncodedField encodedField : clazz.fields()) {
+        DexField field = encodedField.field;
+        DexField original = appView.graphLense().getOriginalFieldSignature(field);
+        if (original != field) {
+          DexField existing = originalFieldSignatures.forcePut(field, original);
+          assert existing == null;
+        }
+      }
+
+      // Record original method signatures.
+      for (DexEncodedMethod encodedMethod : clazz.methods()) {
+        DexMethod method = encodedMethod.method;
+        DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
+        if (original != method) {
+          DexMethod existing = originalMethodSignatures.inverse().get(original);
+          if (existing == null) {
+            originalMethodSignatures.put(method, original);
+          } else {
+            DexMethod renamed = getRenamedMethodSignature(original);
+            if (renamed == existing) {
+              originalMethodSignaturesForBridges.put(method, original);
+            } else {
+              originalMethodSignatures.forcePut(method, original);
+              originalMethodSignaturesForBridges.put(existing, original);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return originalTypeNames.getOrDefault(type, type);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    return originalFieldSignatures.getOrDefault(field, field);
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    if (originalMethodSignaturesForBridges.containsKey(method)) {
+      return originalMethodSignaturesForBridges.get(method);
+    }
+    return originalMethodSignatures.getOrDefault(method, method);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    return originalFieldSignatures.inverse().getOrDefault(originalField, originalField);
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+    return originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod);
+  }
+
+  @Override
+  public DexType lookupType(DexType type) {
+    if (appView.verticallyMergedClasses() != null
+        && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
+      return lookupType(appView.verticallyMergedClasses().getTargetFor(type));
+    }
+    return originalTypeNames.inverse().getOrDefault(type, type);
+  }
+
+  @Override
+  public GraphLenseLookupResult lookupMethod(
+      DexMethod method, DexMethod context, Invoke.Type type) {
+    return new GraphLenseLookupResult(method, type);
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
+    return RewrittenPrototypeDescription.none();
+  }
+
+  @Override
+  public DexField lookupField(DexField field) {
+    return field;
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 755402d..2042392 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -267,7 +267,8 @@
             encodedMethod,
             graphLense.getOriginalMethodSignature(encodedMethod.method),
             callerPosition,
-            origin);
+            origin,
+            options.getInternalOutputMode());
     return new IRBuilder(encodedMethod, appInfo, source, options, origin, generator, graphLense)
         .build(context);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e02869d..f8fb8e1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -197,7 +197,15 @@
             graphLense.getOriginalMethodSignature(encodedMethod.method),
             null,
             appInfo);
-    IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options, origin);
+    IRBuilder builder =
+        new IRBuilder(
+            encodedMethod,
+            appInfo,
+            source,
+            options,
+            origin,
+            new ValueNumberGenerator(),
+            graphLense);
     return builder.build(encodedMethod);
   }
 
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 44294db..32d3c37 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -812,7 +812,6 @@
     public static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
     public static boolean UNKNOWN_RETURNS_CONSTANT = false;
     public static int UNKNOWN_RETURNED_CONSTANT = -1;
-    public static boolean NOT_PUBLICZED = false;
     public static boolean DOES_NOT_USE_IDNETIFIER_NAME_STRING = false;
     public static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
     public static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
@@ -932,7 +931,6 @@
         DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NORMALLY;
     private boolean returnsConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT;
     private long returnedConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT;
-    private boolean publicized = DefaultOptimizationInfoImpl.NOT_PUBLICZED;
     private InlinePreference inlining = InlinePreference.Default;
     private boolean useIdentifierNameString =
         DefaultOptimizationInfoImpl.DOES_NOT_USE_IDNETIFIER_NAME_STRING;
@@ -978,7 +976,6 @@
       neverReturnsNormally = template.neverReturnsNormally;
       returnsConstant = template.returnsConstant;
       returnedConstant = template.returnedConstant;
-      publicized = template.publicized;
       inlining = template.inlining;
       useIdentifierNameString = template.useIdentifierNameString;
       checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
@@ -1165,16 +1162,6 @@
     }
 
     @Override
-    public void markPublicized() {
-      publicized = true;
-    }
-
-    @Override
-    public void unsetPublicized() {
-      publicized = false;
-    }
-
-    @Override
     public void markUseIdentifierNameString() {
       useIdentifierNameString = true;
     }
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 09094aa..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);
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 3f06bbc..11d9730 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -74,6 +74,7 @@
 
         private int argumentIndex = -1;
         private boolean isAlwaysNull = false;
+        private DexType type = null;
 
         public Builder setArgumentIndex(int argumentIndex) {
           this.argumentIndex = argumentIndex;
@@ -85,18 +86,26 @@
           return this;
         }
 
+        public Builder setType(DexType type) {
+          this.type = type;
+          return this;
+        }
+
         public RemovedArgumentInfo build() {
           assert argumentIndex >= 0;
-          return new RemovedArgumentInfo(argumentIndex, isAlwaysNull);
+          assert type != null;
+          return new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type);
         }
       }
 
       private final int argumentIndex;
       private final boolean isAlwaysNull;
+      private final DexType type;
 
-      private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull) {
+      private RemovedArgumentInfo(int argumentIndex, boolean isAlwaysNull, DexType type) {
         this.argumentIndex = argumentIndex;
         this.isAlwaysNull = isAlwaysNull;
+        this.type = type;
       }
 
       public static Builder builder() {
@@ -107,6 +116,10 @@
         return argumentIndex;
       }
 
+      public DexType getType() {
+        return type;
+      }
+
       public boolean isAlwaysNull() {
         return isAlwaysNull;
       }
@@ -117,7 +130,7 @@
 
       public RemovedArgumentInfo withArgumentIndex(int argumentIndex) {
         return this.argumentIndex != argumentIndex
-            ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull)
+            ? new RemovedArgumentInfo(argumentIndex, isAlwaysNull, type)
             : this;
       }
     }
@@ -694,8 +707,8 @@
    * <p>Subclasses can override the lookup methods.
    *
    * <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.
+   * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
+   * invocation type might need to change.
    */
   public static class NestedGraphLense extends GraphLense {
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 55ece50..3170b63 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
@@ -110,6 +111,22 @@
     reader.accept(
         new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
         parsingOptions);
+
+    // Read marker.
+    if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
+        && reader.getItem(CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX) > 0) {
+      try {
+        Object maybeMarker =
+            reader.readConst(
+                CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX,
+                new char[reader.getMaxStringLength()]);
+        if (maybeMarker instanceof String) {
+          application.getFactory().createString((String) maybeMarker);
+        }
+      } catch (IllegalArgumentException e) {
+        // Ignore if the type of the constant is not something readConst() allows.
+      }
+    }
   }
 
   private static int cleanAccessFlags(int access) {
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/UpdatableOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
index aefd2a8..8751816 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableOptimizationInfo.java
@@ -42,8 +42,4 @@
   void unsetForceInline();
 
   void markNeverInline();
-
-  void markPublicized();
-
-  void unsetPublicized();
 }
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/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 04e9a1b..692dba2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -98,6 +98,9 @@
 
   @Override
   public boolean instructionInstanceCanThrow() {
+    if (throwingInfo == ThrowingInfo.NO_THROW) {
+      return false;
+    }
     // The const-string instruction can be a throwing instruction in DEX, if decode() fails.
     try {
       value.toString();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index fa4e6c5..c3b8f80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
@@ -20,22 +21,27 @@
 
   private final DexReference item;
   private final ClassNameComputationInfo classNameComputationInfo;
+  private final ThrowingInfo throwingInfo;
 
-  public DexItemBasedConstString(Value dest, DexReference item) {
-    this(dest, item, ClassNameComputationInfo.none());
+  public DexItemBasedConstString(Value dest, DexReference item, ThrowingInfo throwingInfo) {
+    this(dest, item, throwingInfo, ClassNameComputationInfo.none());
   }
 
   public DexItemBasedConstString(
-      Value dest, DexReference item, ClassNameComputationInfo classNameComputationInfo) {
+      Value dest,
+      DexReference item,
+      ThrowingInfo throwingInfo,
+      ClassNameComputationInfo classNameComputationInfo) {
     super(dest);
     dest.markNeverNull();
     this.item = item;
     this.classNameComputationInfo = classNameComputationInfo;
+    this.throwingInfo = throwingInfo;
   }
 
   public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
     return new DexItemBasedConstString(
-        newValue, original.getItem(), original.classNameComputationInfo);
+        newValue, original.getItem(), original.throwingInfo, original.classNameComputationInfo);
   }
 
   public DexReference getItem() {
@@ -88,7 +94,7 @@
 
   @Override
   public boolean instructionTypeCanThrow() {
-    return true;
+    return throwingInfo == ThrowingInfo.CAN_THROW;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index a852d28..d5f86fa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -105,8 +105,8 @@
   }
 
   @Override
-  public InlineAction computeInlining(InliningOracle decider, DexType inocationContext) {
-    return decider.computeForInvokeStatic(this, inocationContext);
+  public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+    return decider.computeForInvokeStatic(this, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index a745659..2c10fbd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.conversion.CfState.Snapshot;
 import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOutputMode;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -196,13 +197,15 @@
   private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
   private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
   private final CanonicalPositions canonicalPositions;
+  private final InternalOutputMode internalOutputMode;
 
   public CfSourceCode(
       CfCode code,
       DexEncodedMethod method,
       DexMethod originalMethod,
       Position callerPosition,
-      Origin origin) {
+      Origin origin,
+      InternalOutputMode internalOutputMode) {
     this.code = code;
     this.method = method;
     this.origin = origin;
@@ -218,6 +221,7 @@
     }
     this.state = new CfState(origin);
     canonicalPositions = new CanonicalPositions(callerPosition, cfPositionCount, originalMethod);
+    this.internalOutputMode = internalOutputMode;
   }
 
   @Override
@@ -249,8 +253,9 @@
   // Utility method that treats constant strings as not throwing in the case of having CF output.
   // This is the only instruction that differ in throwing between DEX and CF. If we find more
   // consider rewriting CfInstruction.canThrow to take in options.
-  private boolean canThrowHelper(IRBuilder builder, CfInstruction instruction) {
-    if (builder.isGeneratingClassFiles() && instruction.isConstString()) {
+  private boolean canThrowHelper(CfInstruction instruction) {
+    if (internalOutputMode.isGeneratingClassFiles()
+        && (instruction.isConstString() || instruction.isDexItemBasedConstString())) {
       return false;
     }
     return instruction.canThrow();
@@ -259,7 +264,8 @@
   @Override
   public int traceInstruction(int instructionIndex, IRBuilder builder) {
     CfInstruction instruction = code.getInstructions().get(instructionIndex);
-    if (canThrowHelper(builder, instruction)) {
+    assert builder.isGeneratingClassFiles() == internalOutputMode.isGeneratingClassFiles();
+    if (canThrowHelper(instruction)) {
       TryHandlerList tryHandlers = getTryHandlers(instructionIndex);
       if (!tryHandlers.isEmpty()) {
         // Ensure the block starts at the start of the try-range (don't enqueue, not a target).
@@ -433,7 +439,7 @@
     assert currentBlockInfo != null;
     setLocalVariableLists();
 
-    if (canThrowHelper(builder, instruction)) {
+    if (canThrowHelper(instruction)) {
       Snapshot exceptionTransfer =
           state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
       for (int target : currentBlockInfo.exceptionalSuccessors) {
@@ -632,7 +638,7 @@
 
   @Override
   public boolean verifyCurrentInstructionCanThrow() {
-    return code.getInstructions().get(currentInstructionIndex).canThrow();
+    return canThrowHelper(code.getInstructions().get(currentInstructionIndex));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 881b255..4738403 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -38,9 +38,9 @@
 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.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CanonicalPositions;
@@ -50,14 +50,14 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 
 public class DexSourceCode implements SourceCode {
 
   private final DexCode code;
-  private final MethodAccessFlags accessFlags;
-  private final DexProto proto;
+  private final DexEncodedMethod method;
 
   // Mapping from instruction offset to instruction index in the DexCode instruction array.
   private final Map<Integer, Integer> offsetToInstructionIndex = new HashMap<>();
@@ -73,8 +73,6 @@
   private Position currentPosition = null;
   private final CanonicalPositions canonicalPositions;
 
-  private final List<TypeLatticeElement> argumentTypes;
-
   private List<DexDebugEntry> debugEntries = null;
   // In case of inlining the position of the invoke in the caller.
   private final DexMethod originalMethod;
@@ -86,11 +84,8 @@
       Position callerPosition,
       AppInfo appInfo) {
     this.code = code;
-    this.proto = method.method.proto;
-    this.accessFlags = method.accessFlags;
+    this.method = method;
     this.originalMethod = originalMethod;
-
-    argumentTypes = computeArgumentTypes(appInfo);
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
       debugEntries = info.computeEntries(originalMethod);
@@ -149,16 +144,46 @@
     if (code.incomingRegisterSize == 0) {
       return;
     }
+
+    RemovedArgumentsInfo removedArgumentsInfo = builder.prototypeChanges.getRemovedArgumentsInfo();
+    ListIterator<RemovedArgumentInfo> removedArgumentIterator = removedArgumentsInfo.iterator();
+    RemovedArgumentInfo nextRemovedArgument =
+        removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+
     // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument
     // block.
+    int argumentIndex = 0;
+
     int register = code.registerSize - code.incomingRegisterSize;
-    if (!accessFlags.isStatic()) {
+    if (!method.isStatic()) {
       builder.addThisArgument(register);
+      ++argumentIndex;
       ++register;
     }
-    for (TypeLatticeElement typeLattice : argumentTypes) {
-      builder.addNonThisArgument(register, typeLattice);
-      register += typeLattice.requiredRegisters();
+
+    int numberOfArguments =
+        method.method.proto.parameters.values.length
+            + removedArgumentsInfo.numberOfRemovedArguments()
+            + (method.isStatic() ? 0 : 1);
+
+    for (int usedArgumentIndex = 0; argumentIndex < numberOfArguments; ++argumentIndex) {
+      TypeLatticeElement type;
+      if (nextRemovedArgument != null && nextRemovedArgument.getArgumentIndex() == argumentIndex) {
+        type =
+            TypeLatticeElement.fromDexType(
+                nextRemovedArgument.getType(), Nullability.maybeNull(), builder.getAppInfo());
+        builder.addConstantOrUnusedArgument(register);
+        nextRemovedArgument =
+            removedArgumentIterator.hasNext() ? removedArgumentIterator.next() : null;
+      } else {
+        type =
+            TypeLatticeElement.fromDexType(
+                method.method.proto.parameters.values[usedArgumentIndex++],
+                Nullability.maybeNull(),
+                builder.getAppInfo());
+        builder.addNonThisArgument(register, type);
+      }
+      register += type.requiredRegisters();
     }
     builder.flushArgumentInstructions();
   }
@@ -310,14 +335,6 @@
         arrayFilledDataPayloadResolver.getData(payloadOffset));
   }
 
-  private List<TypeLatticeElement> computeArgumentTypes(AppInfo appInfo) {
-    List<TypeLatticeElement> types = new ArrayList<>(proto.parameters.size());
-    for (DexType type : proto.parameters.values) {
-      types.add(TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appInfo));
-    }
-    return types;
-  }
-
   private boolean isInvoke(Instruction dex) {
     return dex instanceof InvokeDirect
         || dex instanceof InvokeDirectRange
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 045ba3e..31b0ec6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -396,7 +396,7 @@
   private DexEncodedMethod context;
   private final AppInfo appInfo;
   private final Origin origin;
-  private RewrittenPrototypeDescription prototypeChanges;
+  final RewrittenPrototypeDescription prototypeChanges;
   private ListIterator<RemovedArgumentInfo> removedArgumentsIterator;
   private int argumentCount = 0;
 
@@ -894,15 +894,8 @@
       DebugLocalInfo local = getOutgoingLocal(register);
       Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
       addInstruction(new Argument(value));
-    } else if (removedArgumentInfo.isAlwaysNull()) {
-      if (pendingArgumentInstructions == null) {
-        pendingArgumentInstructions = new ArrayList<>();
-      }
-      DebugLocalInfo local = getOutgoingLocal(register);
-      Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
-      pendingArgumentInstructions.add(new ConstNumber(value, 0));
     } else {
-      assert removedArgumentInfo.isNeverUsed();
+      handleConstantOrUnusedArgument(register, removedArgumentInfo);
     }
   }
 
@@ -918,6 +911,25 @@
     }
   }
 
+  public void addConstantOrUnusedArgument(int register) {
+    handleConstantOrUnusedArgument(register, getRemovedArgumentInfo());
+  }
+
+  private void handleConstantOrUnusedArgument(
+      int register, RemovedArgumentInfo removedArgumentInfo) {
+    assert removedArgumentInfo != null;
+    if (removedArgumentInfo.isAlwaysNull()) {
+      if (pendingArgumentInstructions == null) {
+        pendingArgumentInstructions = new ArrayList<>();
+      }
+      DebugLocalInfo local = getOutgoingLocal(register);
+      Value value = writeRegister(register, TypeLatticeElement.NULL, ThrowingInfo.NO_THROW, local);
+      pendingArgumentInstructions.add(new ConstNumber(value, 0));
+    } else {
+      assert removedArgumentInfo.isNeverUsed();
+    }
+  }
+
   public void flushArgumentInstructions() {
     if (pendingArgumentInstructions != null) {
       pendingArgumentInstructions.forEach(this::addInstruction);
@@ -1135,11 +1147,14 @@
     add(instruction);
   }
 
+  private ThrowingInfo throwingInfoForConstStrings() {
+    return options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+  }
+
   public void addConstString(int dest, DexString string) {
     TypeLatticeElement typeLattice =
         TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
-    ThrowingInfo throwingInfo =
-        options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+    ThrowingInfo throwingInfo = throwingInfoForConstStrings();
     add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string, throwingInfo));
   }
 
@@ -1147,8 +1162,9 @@
     assert method.getOptimizationInfo().useIdentifierNameString();
     TypeLatticeElement typeLattice =
         TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
-    Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
-    DexItemBasedConstString instruction = new DexItemBasedConstString(out, item);
+    ThrowingInfo throwingInfo = throwingInfoForConstStrings();
+    Value out = writeRegister(dest, typeLattice, throwingInfo);
+    DexItemBasedConstString instruction = new DexItemBasedConstString(out, item, throwingInfo);
     add(instruction);
   }
 
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 ed1994b..5bf9154 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
@@ -194,7 +194,7 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
             : null;
-    this.stringOptimizer = new StringOptimizer(appInfo, options);
+    this.stringOptimizer = new StringOptimizer(appInfo, options.getInternalOutputMode());
     this.enableWholeProgramOptimizations = appView != null;
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasLiveness();
@@ -1011,7 +1011,8 @@
     codeRewriter.rewriteSwitch(code);
     codeRewriter.processMethodsNeverReturningNormally(code);
     codeRewriter.simplifyIf(code);
-    codeRewriter.redundantConstNumberRemoval(code);
+    // TODO(b/123284765) This produces a runtime-crash in Q. Activate again when fixed.
+    // codeRewriter.redundantConstNumberRemoval(code);
     new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
 
     if (options.testing.invertConditionals) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 4e91c62..a9a571f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -151,6 +151,10 @@
           InvokeStatic invokeStatic = instruction.asInvokeStatic();
           DexMethod method = invokeStatic.getInvokedMethod();
           DexClass clazz = findDefinitionFor(method.holder);
+          if (Java8MethodRewriter.hasJava8MethodRewritePrefix(method.holder)) {
+            // We did not create this code yet, but it will not require rewriting.
+            continue;
+          }
           if (clazz == null) {
             // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
             // exception but we can not report it as error since it can also be the intended
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
index c0a7569..9e2e9ab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -84,6 +84,10 @@
     return null;
   }
 
+  public static boolean hasJava8MethodRewritePrefix(DexType clazz) {
+    return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
+  }
+
   public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
     if (holders.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index e1e534e..568e021 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -65,7 +65,7 @@
         twrUtilityClass, twrCloseResourceProto, factory.twrCloseResourceMethodName);
   }
 
-  // Rewrites calls to $closeResource() method. Can me invoked concurrently.
+  // Rewrites calls to $closeResource() method. Can be invoked concurrently.
   public void rewriteMethodCode(IRCode code) {
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
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 7758d83..fbdb9f3 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
@@ -2112,6 +2112,16 @@
       return false;
     }
 
+    // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+    // elimination may lead to verification errors. See b/123269162.
+    if (options.canHaveArtCheckCastVerifierBug()) {
+      if (inValue.getTypeLattice().isNullType()
+          && castType.isArrayType()
+          && castType.toBaseType(dexItemFactory).isFloatType()) {
+        return false;
+      }
+    }
+
     // We might see chains of casts on subtypes. It suffices to cast to the lowest subtype,
     // as that will fail if a cast on a supertype would have failed.
     Predicate<Instruction> isCheckcastToSubtype =
@@ -2156,6 +2166,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.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c1364f6..e41593f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -298,7 +298,11 @@
           removedArgumentsInfo = new ArrayList<>();
         }
         removedArgumentsInfo.add(
-            RemovedArgumentInfo.builder().setArgumentIndex(i + offset).setIsAlwaysNull().build());
+            RemovedArgumentInfo.builder()
+                .setArgumentIndex(i + offset)
+                .setIsAlwaysNull()
+                .setType(type)
+                .build());
       }
     }
     return removedArgumentsInfo != null
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 061907f..aae8429 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -208,8 +208,8 @@
       return null;
     }
     assert method.getCode().getOwner() == method;
-    int argumentCount =
-        method.method.proto.parameters.size() + (method.accessFlags.isStatic() ? 0 : 1);
+    int offset = method.accessFlags.isStatic() ? 0 : 1;
+    int argumentCount = method.method.proto.parameters.size() + offset;
     // TODO(65810338): Implement for virtual methods as well.
     if (method.accessFlags.isPrivate()
         || method.accessFlags.isStatic()
@@ -224,9 +224,13 @@
       BitSet used = collector.getUsedArguments();
       if (used.cardinality() < argumentCount) {
         List<RemovedArgumentInfo> unused = new ArrayList<>();
-        for (int i = 0; i < argumentCount; i++) {
-          if (!used.get(i)) {
-            unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
+        for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
+          if (!used.get(argumentIndex)) {
+            unused.add(
+                RemovedArgumentInfo.builder()
+                    .setArgumentIndex(argumentIndex)
+                    .setType(method.method.proto.parameters.values[argumentIndex - offset])
+                    .build());
           }
         }
         return new RemovedArgumentsInfo(unused);
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/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 0ff9331..fd326d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -32,7 +32,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
 import com.google.common.annotations.VisibleForTesting;
 import java.util.Set;
 import java.util.function.BiFunction;
@@ -44,11 +44,11 @@
   private final DexItemFactory factory;
   private final ThrowingInfo throwingInfo;
 
-  public StringOptimizer(AppInfo appInfo, InternalOptions options) {
+  public StringOptimizer(AppInfo appInfo, InternalOutputMode outputMode) {
     this.appInfo = appInfo;
     this.factory = appInfo.dexItemFactory;
-    throwingInfo = options.isGeneratingClassFiles()
-        ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+    this.throwingInfo =
+        outputMode.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
   }
 
   // int String#length()
@@ -194,8 +194,12 @@
       String name = null;
       if (invokedMethod == factory.classMethods.getName) {
         if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
-          deferred = new DexItemBasedConstString(
-              invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
+          deferred =
+              new DexItemBasedConstString(
+                  invoke.outValue(),
+                  baseType,
+                  throwingInfo,
+                  new ClassNameComputationInfo(NAME, arrayDepth));
         } else {
           name = computeClassName(descriptor, holder, NAME, arrayDepth);
         }
@@ -218,6 +222,7 @@
                 new DexItemBasedConstString(
                     invoke.outValue(),
                     baseType,
+                    throwingInfo,
                     new ClassNameComputationInfo(CANONICAL_NAME, arrayDepth));
           } else {
             name = computeClassName(descriptor, holder, CANONICAL_NAME, arrayDepth);
@@ -234,8 +239,12 @@
             continue;
           }
           if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
-            deferred = new DexItemBasedConstString(
-                invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
+            deferred =
+                new DexItemBasedConstString(
+                    invoke.outValue(),
+                    baseType,
+                    throwingInfo,
+                    new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
           } else {
             name = computeClassName(descriptor, holder, SIMPLE_NAME, arrayDepth);
           }
@@ -313,8 +322,8 @@
               code.createValue(
                   TypeLatticeElement.stringClassType(appInfo, definitelyNotNull()),
                   invoke.getLocalInfo());
-          ConstString nullString = new ConstString(
-              nullStringValue, factory.createString("null"), throwingInfo);
+          ConstString nullString =
+              new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
           it.replaceCurrentInstruction(nullString);
         } else if (inType.nullability().isDefinitelyNotNull()
             && inType.isClassType()
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 14459fe..05120db 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.Code;
@@ -61,10 +62,15 @@
   private static final boolean RUN_VERIFIER = false;
   private static final boolean PRINT_CF = false;
 
+  // First item inserted into the constant pool is the marker string which generates an UTF8 to
+  // pool index #1 and a String entry to #2, referencing #1.
+  public static final int MARKER_STRING_CONSTANT_POOL_INDEX = 2;
+
   private final DexApplication application;
   private final GraphLense graphLense;
   private final NamingLens namingLens;
   private final InternalOptions options;
+  private final String markerString;
 
   public final ProguardMapSupplier proguardMapSupplier;
   public final String deadCode;
@@ -73,6 +79,7 @@
   public CfApplicationWriter(
       DexApplication application,
       InternalOptions options,
+      Marker marker,
       String deadCode,
       GraphLense graphLense,
       NamingLens namingLens,
@@ -82,6 +89,7 @@
     this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.options = options;
+    this.markerString = marker == null ? null : marker.toString();
     this.proguardMapSupplier = proguardMapSupplier;
     this.deadCode = deadCode;
     this.proguardSeedsData = proguardSeedsData;
@@ -117,6 +125,10 @@
 
   private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
     ClassWriter writer = new ClassWriter(0);
+    if (markerString != null) {
+      int index = writer.newConst(markerString);
+      assert index == MARKER_STRING_CONSTANT_POOL_INDEX;
+    }
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
     int version = getClassFileVersion(clazz);
     int access = clazz.accessFlags.getAsCfAccessFlags();
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 95fcfd2..0638816 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -87,6 +88,8 @@
     if (!code.hasConstString) {
       return;
     }
+    final ThrowingInfo throwingInfo =
+        options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.NO_THROW;
     DexType originHolder = code.method.method.getHolder();
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
@@ -131,7 +134,8 @@
           iterator.previous();
           // Prepare $decoupled just before $fieldPut
           Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
-          DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+          DexItemBasedConstString decoupled =
+              new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
           decoupled.setPosition(fieldPut.getPosition());
           // If the current block has catch handler, split into two blocks.
           // Because const-string we're about to add is also a throwing instr, we need to split
@@ -191,7 +195,8 @@
             iterator.previous();
             // Prepare $decoupled just before $invoke
             Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
-            DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+            DexItemBasedConstString decoupled =
+                new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
             decoupled.setPosition(invoke.getPosition());
             changes[positionOfIdentifier] = newIn;
             // If the current block has catch handler, split into two blocks.
@@ -238,7 +243,7 @@
               // Prepare $decoupled just before $invoke
               Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
               DexItemBasedConstString decoupled =
-                  new DexItemBasedConstString(newIn, itemBasedString);
+                  new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
               decoupled.setPosition(invoke.getPosition());
               changes[i] = newIn;
               // If the current block has catch handler, split into two blocks.
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index cdf96c2..706620f 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -171,6 +171,11 @@
         return null;
       }
       DexType holderType = classValue.getConstInstruction().asConstClass().getValue();
+      if (holderType.isArrayType()) {
+        // None of the fields or methods of an array type will be renamed, since they are all
+        // declared in the library. Hence there is no need to handle this case.
+        return null;
+      }
       DexClass holder = appInfo.definitionFor(holderType);
       if (holder == null) {
         return null;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 9934ab2..5547315 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -318,7 +318,8 @@
       throw new ParseException("Identifier expected");
     }
     nextCodePoint();
-    while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
+    while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())
+        || IdentifierUtils.isQuestionMark(peekCodePoint())) {
       nextCodePoint();
     }
     if (isInit) {
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 72fc987..d3f336f 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -133,7 +133,6 @@
       // Although the current method became public, it surely has the single virtual target.
       encodedMethod.method.setSingleVirtualMethodCache(
           encodedMethod.method.getHolder(), encodedMethod);
-      encodedMethod.getMutableOptimizationInfo().markPublicized();
       return true;
     }
 
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..0761a71 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1460,7 +1460,10 @@
               new RootSetBuilder(appView, rootSet.ifRules, options);
           IfRuleEvaluator ifRuleEvaluator =
               consequentSetBuilder.getIfRuleEvaluator(
-                  liveMethods.getItems(), liveFields.getItems(), executorService);
+                  liveFields.getItems(),
+                  liveMethods.getItems(),
+                  targetedMethods.getItems(),
+                  executorService);
           ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
           enqueueRootItems(consequentRootSet.noShrinking);
           rootSet.neverInline.addAll(consequentRootSet.neverInline);
@@ -1736,6 +1739,14 @@
           markInstantiated(holderClass.type, KeepReason.reflectiveUseIn(method));
         }
         markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
+        // Fields accessed by reflection is marked as both read and written.
+        if (encodedField.isStatic()) {
+          registerItemWithTargetAndContext(staticFieldsRead, encodedField.field, method);
+          registerItemWithTargetAndContext(staticFieldsWritten, encodedField.field, method);
+        } else {
+          registerItemWithTargetAndContext(instanceFieldsRead, encodedField.field, method);
+          registerItemWithTargetAndContext(instanceFieldsWritten, encodedField.field, method);
+        }
       }
     } else {
       assert identifierItem.isDexMethod();
@@ -2414,6 +2425,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..203cf82 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -277,26 +277,31 @@
   }
 
   IfRuleEvaluator getIfRuleEvaluator(
-      Set<DexEncodedMethod> liveMethods,
       Set<DexEncodedField> liveFields,
+      Set<DexEncodedMethod> liveMethods,
+      Set<DexEncodedMethod> targetedMethods,
       ExecutorService executorService) {
-    return new IfRuleEvaluator(liveMethods, liveFields, executorService);
+    return new IfRuleEvaluator(liveFields, liveMethods, targetedMethods, executorService);
   }
 
   class IfRuleEvaluator {
 
-    private final Set<DexEncodedMethod> liveMethods;
     private final Set<DexEncodedField> liveFields;
+    private final Set<DexEncodedMethod> liveMethods;
+    private final Set<DexEncodedMethod> targetedMethods;
+
     private final ExecutorService executorService;
 
     private final List<Future<?>> futures = new ArrayList<>();
 
     public IfRuleEvaluator(
-        Set<DexEncodedMethod> liveMethods,
         Set<DexEncodedField> liveFields,
+        Set<DexEncodedMethod> liveMethods,
+        Set<DexEncodedMethod> targetedMethods,
         ExecutorService executorService) {
-      this.liveMethods = liveMethods;
       this.liveFields = liveFields;
+      this.liveMethods = liveMethods;
+      this.targetedMethods = targetedMethods;
       this.executorService = executorService;
     }
 
@@ -391,7 +396,7 @@
           filteredMembers,
           targetClass.methods(
               m ->
-                  liveMethods.contains(m)
+                  (liveMethods.contains(m) || targetedMethods.contains(m))
                       && appView.graphLense().getOriginalMethodSignature(m.method).getHolder()
                           == sourceClass.type));
 
@@ -843,6 +848,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/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index d793e3b..15dcafa 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -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())) {
@@ -743,10 +744,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;
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 69d65ea..2276790 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -150,7 +150,7 @@
 
   private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
     try {
-      ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
+      ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.DEFLATED);
     } catch (IOException e) {
       handleIOException(e, handler);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3015132..3997982 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -898,4 +898,12 @@
   public boolean canHaveExceptionTypeBug() {
     return minApiLevel < AndroidApiLevel.Q.getLevel();
   }
+
+  // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
+  // aget instruction. We therefore need to be careful when performing trivial check-cast
+  // elimination of check-cast instructions where the value being cast is the constant null.
+  // See b/123269162.
+  public boolean canHaveArtCheckCastVerifierBug() {
+    return minApiLevel < AndroidApiLevel.J.getLevel();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 3b2b878..1cf61c0 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import java.util.List;
@@ -68,6 +69,7 @@
 
   private static boolean assumeClassesAreEqual(DexProgramClass a) {
     return LambdaRewriter.hasLambdaClassPrefix(a.type)
+        || Java8MethodRewriter.hasJava8MethodRewritePrefix(a.type)
         || InterfaceMethodRewriter.hasDispatchClassSuffix(a.type)
         || a.type.descriptor.toString().equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR);
   }
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/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 9118fda..84b6675 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -89,10 +90,11 @@
         Assert.assertNotNull(mainClassDescriptor);
         for (String descriptor : descriptors) {
           // classes are either lambda classes used by the main class, companion classes of the main
-          // interface or the main class/interface
+          // interface, the main class/interface, or for JDK9, desugaring of try-with-resources.
           Assert.assertTrue(descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
               || descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
               || descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
+              || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
               || descriptor.equals(mainClassDescriptor));
         }
         String classDescriptor =
@@ -188,11 +190,11 @@
       }
     }
 
-    ProgramResource mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
+    AndroidApp mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
       return mergeClassFiles(dexFiles, out, OutputMode.DexIndexed);
     }
 
-    ProgramResource mergeClassFiles(
+    AndroidApp mergeClassFiles(
         List<ProgramResource> dexFiles, Path outputPath, OutputMode outputMode) throws Throwable {
       Builder builder = D8Command.builder();
       for (ProgramResource dexFile : dexFiles) {
@@ -213,7 +215,7 @@
       try {
         AndroidApp app = ToolHelper.runD8(builder, this::combinedOptionConsumer);
         assert app.getDexProgramResourcesForTesting().size() == 1;
-        return app.getDexProgramResourcesForTesting().get(0);
+        return app;
       } catch (Unimplemented | CompilationError | InternalCompilerError re) {
         throw re;
       } catch (RuntimeException re) {
@@ -247,13 +249,16 @@
       Assert.assertArrayEquals(readResource(entry.getValue()), readResource(otherResource));
     }
 
-    ProgramResource mergedFromCompiledSeparately =
+    AndroidApp mergedFromCompiledSeparately =
         test.mergeClassFiles(Lists.newArrayList(compiledSeparately.values()), null);
-    ProgramResource mergedFromCompiledTogether =
+    AndroidApp mergedFromCompiledTogether =
         test.mergeClassFiles(Lists.newArrayList(compiledTogether.values()), null);
+
+    // TODO(b/123504206): Add a main method and test the output runs.
+
     Assert.assertArrayEquals(
-        readResource(mergedFromCompiledSeparately),
-        readResource(mergedFromCompiledTogether));
+        readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+        readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
   }
 
   @Test
@@ -267,16 +272,24 @@
     D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
     test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
 
-    ProgramResource mergedFromCompiledSeparately =
+    AndroidApp mergedFromCompiledSeparately =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
-    ProgramResource mergedFromCompiledTogether =
+    AndroidApp mergedFromCompiledTogether =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
 
+    Path out1 = temp.newFolder().toPath().resolve("out-together.zip");
+    mergedFromCompiledTogether.writeToZip(out1, OutputMode.DexIndexed);
+    ToolHelper.runArtNoVerificationErrors(out1.toString(), testPackage + "." + mainClass);
+
+    Path out2 = temp.newFolder().toPath().resolve("out-separate.zip");
+    mergedFromCompiledSeparately.writeToZip(out2, OutputMode.DexIndexed);
+    ToolHelper.runArtNoVerificationErrors(out2.toString(), testPackage + "." + mainClass);
+
     Assert.assertArrayEquals(
-        readResource(mergedFromCompiledSeparately),
-        readResource(mergedFromCompiledTogether));
+        readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+        readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
   }
 
   @Test
@@ -290,16 +303,19 @@
     D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
     test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
 
-    ProgramResource mergedFromCompiledSeparately =
+    AndroidApp mergedFromCompiledSeparately =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
-    ProgramResource mergedFromCompiledTogether =
+    AndroidApp mergedFromCompiledTogether =
         test.mergeClassFiles(
             Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
 
+    // TODO(b/123504206): This test throws an index out of bounds exception.
+    // Re-write or verify running fails in the expected way.
+
     Assert.assertArrayEquals(
-        readResource(mergedFromCompiledSeparately),
-        readResource(mergedFromCompiledTogether));
+        readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+        readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
   }
 
   @Test
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/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 5a12e62..6a47607 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -15,8 +16,9 @@
 public class ExtractMarkerTest {
 
   @Test
-  public void extractMarkerTest() throws CompilationFailedException {
+  public void extractMarkerTestDex() throws CompilationFailedException {
     String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+    boolean[] testExecuted = {false};
     D8.run(
         D8Command.builder()
             .addProgramFiles(Paths.get(classFile))
@@ -42,8 +44,46 @@
                     assertEquals(
                         CompilationMode.DEBUG.toString().toLowerCase(),
                         marker.getCompilationMode());
+                    testExecuted[0] = true;
                   }
                 })
             .build());
+    assertTrue(testExecuted[0]);
+  }
+
+  @Test
+  public void extractMarkerTestCf() throws CompilationFailedException {
+    String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+    boolean[] testExecuted = {false};
+    R8.run(
+        R8Command.builder()
+            .addProgramFiles(Paths.get(classFile))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setProgramConsumer(
+                new ClassFileConsumer.ForwardingConsumer(null) {
+                  @Override
+                  public void accept(
+                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                    Marker marker;
+                    try {
+                      Collection<Marker> markers =
+                          ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+                      assertEquals(1, markers.size());
+                      marker = markers.iterator().next();
+                    } catch (Exception e) {
+                      throw new RuntimeException(e);
+                    }
+                    assertEquals(Tool.R8, marker.getTool());
+                    assertEquals(Version.LABEL, marker.getVersion());
+                    assertEquals(
+                        CompilationMode.DEBUG.toString().toLowerCase(),
+                        marker.getCompilationMode());
+                    testExecuted[0] = true;
+                  }
+                })
+            .build());
+    assertTrue(testExecuted[0]);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 42870fa..55730eb 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
 public class R8TestRunResult extends TestRunResult<R8TestRunResult> {
 
@@ -50,6 +51,13 @@
     return graphInspector.get();
   }
 
+  public R8TestRunResult  inspectGraph(Consumer<GraphInspector> consumer)
+      throws IOException, ExecutionException {
+    consumer.accept(graphInspector());
+    return self();
+  }
+
+
   public String proguardMap() {
     return proguardMap;
   }
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/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 688c91c..89561f8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -464,6 +464,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/deadcode/RemoveDeadArray.java b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
index 5de7de0..b522155 100644
--- a/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
+++ b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
@@ -66,10 +66,9 @@
             .compile();
     CodeInspector inspector = result.inspector();
     assertFalse(inspector.clazz(TestClass.class).clinit().isPresent());
-
     MethodSubject main = inspector.clazz(TestClass.class).mainMethod();
-    main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray());
-    assertFalse(main.getMethod().getCode().asDexCode().toString().contains("NewArray"));
+    assertTrue(
+        main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray()));
     runOnArt(result.app, TestClass.class.getName());
  }
 
@@ -83,10 +82,8 @@
     CodeInspector inspector = result.inspector();
     MethodSubject clinit = inspector.clazz(TestClassWithCatch.class).clinit();
     assertTrue(clinit.isPresent());
-    // Ensure that our optimization does not hit, we should still have 4 Aput instructions.
-    long aPutCount = Arrays.stream(clinit.getMethod().getCode().asDexCode().instructions)
-        .filter(instruction -> instruction instanceof  Aput)
-        .count();
-    assertEquals(4, aPutCount);
+    // Ensure that our optimization does not hit, we should still have 4 ArrayPut instructions.
+    long count = clinit.streamInstructions().filter(a -> a.isArrayPut()).count();
+    assertEquals(4, count);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index fb61f45..1c0287e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -50,6 +51,7 @@
 import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
 import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ObjectReferenceCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
@@ -153,6 +155,12 @@
   }
 
   protected final void runDebugTest(
+      DebugTestConfig config, Class<?> debuggeeClass, JUnit3Wrapper.Command... commands)
+      throws Throwable {
+    runInternal(config, debuggeeClass.getTypeName(), Arrays.asList(commands));
+  }
+
+  protected final void runDebugTest(
       DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
       throws Throwable {
     runInternal(config, debuggeeClass, Arrays.asList(commands));
@@ -324,6 +332,10 @@
     return new JUnit3Wrapper.Command.RunCommand();
   }
 
+  protected final JUnit3Wrapper.Command breakpoint(MethodReference method) {
+    return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName());
+  }
+
   protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
     return breakpoint(className, methodName, null);
   }
@@ -477,6 +489,10 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkLine(int line) {
+    return inspect(t -> t.checkLine(null, line));
+  }
+
   protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
     return inspect(t -> t.checkLine(sourceFile, line));
   }
@@ -532,6 +548,16 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkFieldOnThis(
+      String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(
+        t -> {
+          Value value = t.getFieldOnThis(fieldName, fieldSignature);
+          Assert.assertEquals(
+              "Incorrect value for field 'this." + fieldName + "'", expectedValue, value);
+        });
+  }
+
   protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
     return t -> inspector.accept(t.debuggeeState);
   }
@@ -1298,6 +1324,7 @@
 
         @Override
         public void checkLine(String sourceFile, int line) {
+          sourceFile = sourceFile != null ? sourceFile : getSourceFile();
           if (!Objects.equals(sourceFile, getSourceFile()) || line != getLineNumber()) {
             String locationString = convertCurrentLocationToString();
             Assert.fail(
@@ -1501,7 +1528,15 @@
 
         // The class is available, lookup and read the field.
         long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
-        return getField(getMirror(), classId, fieldId);
+        return internalStaticField(getMirror(), classId, fieldId);
+      }
+
+      public Value getFieldOnThis(String fieldName, String fieldSignature) {
+        long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+        long classId = getMirror().getReferenceType(thisObjectId);
+        // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+        long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+        return internalInstanceField(getMirror(), thisObjectId, fieldId);
       }
 
       private long findField(VmMirror mirror, long classId, String fieldName,
@@ -1547,10 +1582,10 @@
         return matchingFieldIds.getLong(0);
       }
 
-      private Value getField(VmMirror mirror, long classId, long fieldId) {
-
-        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
-            ReferenceTypeCommandSet.GetValuesCommand);
+      private static Value internalStaticField(VmMirror mirror, long classId, long fieldId) {
+        CommandPacket commandPacket =
+            new CommandPacket(
+                ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.GetValuesCommand);
         commandPacket.setNextValueAsReferenceTypeID(classId);
         commandPacket.setNextValueAsInt(1);
         commandPacket.setNextValueAsFieldID(fieldId);
@@ -1565,6 +1600,23 @@
       }
     }
 
+    private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
+      CommandPacket commandPacket =
+          new CommandPacket(
+              ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.GetValuesCommand);
+      commandPacket.setNextValueAsObjectID(objectId);
+      commandPacket.setNextValueAsInt(1);
+      commandPacket.setNextValueAsFieldID(fieldId);
+      ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+      assert replyPacket.getErrorCode() == Error.NONE;
+
+      int fieldsCount = replyPacket.getNextValueAsInt();
+      assert fieldsCount == 1;
+      Value result = replyPacket.getNextValueAsValue();
+      Assert.assertTrue(replyPacket.isAllDataRead());
+      return result;
+    }
+
     public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
         String localName) {
       return getVariablesAt(mirror, location).stream()
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 17d5077..c398e8b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -255,7 +255,8 @@
 
   @Test
   public void testRegress62300145() throws Exception {
-    testDebugging("regress_62300145", "Regress");
+    // TODO(b/67936230): Executes differently for Java 8 and 9, so don't compare to DEX output.
+    testDebuggingJvmOnly("regress_62300145", "Regress");
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
new file mode 100644
index 0000000..9991b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
@@ -0,0 +1,32 @@
+// 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.debug;
+
+public class LambdaOuterContextTest {
+
+  public interface Converter {
+    String convert(int value);
+  }
+
+  public int outer;
+
+  public LambdaOuterContextTest(int outer) {
+    this.outer = outer;
+  }
+
+  public void foo(Converter converter) {
+    System.out.println(converter.convert(outer));
+  }
+
+  public void bar(int arg) {
+    foo(value -> {
+      // Ensure lambda uses parts of the outer context, otherwise javac will optimize it out.
+      return Integer.toString(outer + value + arg);
+    });
+  }
+
+  public static void main(String[] args) {
+    new LambdaOuterContextTest(args.length + 42).bar(args.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
new file mode 100644
index 0000000..8af2a6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -0,0 +1,67 @@
+// 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.debug;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LambdaOuterContextTestRunner extends DebugTestBase {
+
+  public static final Class<?> CLASS = LambdaOuterContextTest.class;
+  public static final String EXPECTED = StringUtils.lines("84");
+
+  @Test
+  public void testJvm() throws Throwable {
+    JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
+    jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(jvmTestBuilder.debugConfig());
+  }
+
+  @Test
+  @Ignore("b/123068053")
+  public void testD8() throws Throwable {
+    D8TestCompileResult compileResult =
+        testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  @Test
+  public void testR8Cf() throws Throwable {
+    R8TestCompileResult compileResult =
+        testForR8(Backend.CF)
+            .addProgramClassesAndInnerClasses(CLASS)
+            .debug()
+            .noMinification()
+            .noTreeShaking()
+            .compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  private void runDebugger(DebugTestConfig config) throws Throwable {
+    runDebugTest(
+        config,
+        CLASS,
+        breakpoint(methodFromMethod(CLASS.getMethod("foo", Converter.class))),
+        run(),
+        checkLine(19),
+        checkLocals("this", "converter"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        stepInto(INTELLIJ_FILTER),
+        checkLine(25),
+        checkLocals("this", "value", "arg"),
+        checkNoLocal("outer"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 1b413ba..f9e6c34 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -13,7 +13,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-// TODO(shertz) test local variables
 @RunWith(Parameterized.class)
 public class LambdaTest extends DebugTestBase {
 
@@ -47,8 +46,11 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 12),
+        checkLocals("i"),
+        checkNoLocal("j"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 16),
+        checkLocals("i", "j"),
         run());
   }
 
@@ -63,8 +65,10 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 32),
+        checkLocals("i", "a", "b"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 37),
+        checkLocals("a", "b"),
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
index f94d5a5..4d5ade8 100644
--- a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.desugar;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -26,6 +28,60 @@
         .assertSuccessWithOutput(expectedOutput);
   }
 
+  @Test
+  public void testD8Merge() throws Exception {
+    String jvmOutput = testForJvm()
+        .addTestClasspath()
+        .run(MergeRun.class).getStdOut();
+    // See b/123242448
+    Path zip1 = temp.newFile("first.zip").toPath();
+    Path zip2 = temp.newFile("second.zip").toPath();
+
+    testForD8()
+        .setMinApi(AndroidApiLevel.L)
+        .addProgramClasses(MergeRun.class, MergeInputB.class)
+        .compile()
+        .assertNoMessages()
+        .writeToZip(zip1);
+    testForD8()
+        .setMinApi(AndroidApiLevel.L)
+        .addProgramClasses(MergeInputA.class)
+        .compile()
+        .assertNoMessages()
+        .writeToZip(zip2);
+    testForD8()
+        .addProgramFiles(zip1, zip2)
+        .setMinApi(AndroidApiLevel.L)
+        .compile()
+        .assertNoMessages()
+        .run(MergeRun.class)
+        .assertSuccessWithOutput(jvmOutput);
+  }
+
+
+  static class MergeInputA {
+    public void foo() {
+      System.out.println(Integer.hashCode(42));
+      System.out.println(Double.hashCode(42.0));
+    }
+  }
+
+  static class MergeInputB {
+    public void foo() {
+      System.out.println(Integer.hashCode(43));
+      System.out.println(Double.hashCode(43.0));
+    }
+  }
+
+  static class MergeRun {
+    public static void main(String[] args) {
+      MergeInputA a = new MergeInputA();
+      MergeInputB b = new MergeInputB();
+      a.foo();
+      b.foo();
+    }
+  }
+
   static class Java8Methods {
     public static void main(String[] args) {
       byte[] aBytes = new byte[]{42, 1, -1, Byte.MAX_VALUE, Byte.MIN_VALUE};
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
index fc382ee..6b281cb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.Streams;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -41,6 +42,7 @@
   }
 
   @Test
+  @Ignore("b/123284765")
   public void test() throws Exception {
     String expectedOutput =
         StringUtils.lines(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
new file mode 100644
index 0000000..8cf8b26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.checkcast;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+/** Regression test for b/123269162. */
+public class TrivialArrayCheckCastTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines(
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException",
+            "Caught NullPointerException", "Caught NullPointerException");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    InternalOptions options = new InternalOptions();
+    options.minApiLevel = AndroidApiLevel.I_MR1.getLevel();
+    assert options.canHaveArtCheckCastVerifierBug();
+
+    testForR8(Backend.DEX)
+        .addInnerClasses(TrivialArrayCheckCastTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(AndroidApiLevel.I_MR1)
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testBooleanArray();
+      testByteArray();
+      testCharArray();
+      testDoubleArray();
+      testFloatArray();
+      testFloatArrayNested();
+      testIntArray();
+      testLongArray();
+      testObjectArray();
+      testShortArray();
+    }
+
+    @NeverInline
+    private static void testBooleanArray() {
+      boolean[] array = (boolean[]) null;
+      try {
+        boolean value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testByteArray() {
+      byte[] array = (byte[]) null;
+      try {
+        byte value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testCharArray() {
+      char[] array = (char[]) null;
+      try {
+        char value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testDoubleArray() {
+      double[] array = (double[]) null;
+      try {
+        double value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testFloatArray() {
+      float[] array = (float[]) null;
+      try {
+        float value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testFloatArrayNested() {
+      float[][] nestedArray = (float[][]) null;
+      try {
+        float[] array = nestedArray[42];
+        float value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testIntArray() {
+      int[] array = (int[]) null;
+      try {
+        int value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testLongArray() {
+      long[] array = (long[]) null;
+      try {
+        long value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testObjectArray() {
+      Object[] array = (Object[]) null;
+      try {
+        Object value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+
+    @NeverInline
+    private static void testShortArray() {
+      short[] array = (short[]) null;
+      try {
+        short value = array[42];
+        System.out.println("Read value: " + value);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        System.out.println("Caught ArrayIndexOutOfBoundsException");
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
new file mode 100644
index 0000000..71c5fbe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningFromCurrentClassTest extends TestBase {
+
+  @Ignore("b/123327416")
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines(
+            "In A.<clinit>()",
+            "In B.<clinit>()",
+            "In A.inlineable1()",
+            "In B.inlineable2()",
+            "In C.<clinit>()",
+            "In C.notInlineable()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(InliningFromCurrentClassTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    ClassSubject classB = inspector.clazz(B.class);
+    assertThat(classB, isPresent());
+
+    ClassSubject classC = inspector.clazz(C.class);
+    assertThat(classC, isPresent());
+
+    MethodSubject inlineable1Method = classA.uniqueMethodWithName("inlineable1");
+    assertThat(inlineable1Method, not(isPresent()));
+
+    MethodSubject inlineable2Method = classB.uniqueMethodWithName("inlineable2");
+    assertThat(inlineable2Method, not(isPresent()));
+
+    MethodSubject notInlineableMethod = classC.uniqueMethodWithName("notInlineable");
+    assertThat(notInlineableMethod, isPresent());
+
+    MethodSubject testMethod = classB.uniqueMethodWithName("test");
+    assertThat(testMethod, isPresent());
+    assertThat(testMethod, not(invokesMethod(inlineable1Method)));
+    assertThat(testMethod, not(invokesMethod(inlineable2Method)));
+    assertThat(testMethod, invokesMethod(notInlineableMethod));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      B.test();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    static {
+      System.out.println("In A.<clinit>()");
+    }
+
+    static void inlineable1() {
+      System.out.println("In A.inlineable1()");
+    }
+  }
+
+  @NeverMerge
+  static class B extends A {
+
+    static {
+      System.out.println("In B.<clinit>()");
+    }
+
+    @NeverInline
+    static void test() {
+      A.inlineable1();
+      B.inlineable2();
+      C.notInlineable();
+    }
+
+    static void inlineable2() {
+      System.out.println("In B.inlineable2()");
+    }
+  }
+
+  static class C extends B {
+
+    static {
+      System.out.println("In C.<clinit>()");
+    }
+
+    static void notInlineable() {
+      System.out.println("In C.notInlineable()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
new file mode 100644
index 0000000..9691149
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.MethodSubject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InliningWithClassInitializerTest extends TestBase {
+
+  @Ignore("b/123327413")
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines("In A.<clinit>()", "In B.inlineable()", "In B.other()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(InliningWithClassInitializerTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classA = inspector.clazz(A.class);
+    assertThat(classA, isPresent());
+
+    ClassSubject classB = inspector.clazz(B.class);
+    assertThat(classB, isPresent());
+
+    MethodSubject inlineableMethod = classB.uniqueMethodWithName("inlineable");
+    assertThat(inlineableMethod, not(isPresent()));
+
+    MethodSubject otherMethod = classB.uniqueMethodWithName("other");
+    assertThat(otherMethod, isPresent());
+
+    MethodSubject mainMethod = inspector.clazz(TestClass.class).mainMethod();
+    assertThat(mainMethod, invokesMethod(otherMethod));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Should be inlined since the call to `B.other()` will ensure that the static initalizer in
+      // A will continue to be executed even after inlining.
+      B.inlineable();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    static {
+      System.out.println("In A.<clinit>()");
+    }
+  }
+
+  static class B extends A {
+
+    static void inlineable() {
+      System.out.println("In B.inlineable()");
+      other();
+    }
+
+    @NeverInline
+    static void other() {
+      System.out.println("In B.other()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 8a8f137..7cb49d3 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -29,7 +29,13 @@
     String mapping =
         "com.c.c.b -> com.c.c.b:\n" +
             "    1287:1287:int ?(int,int) -> ?";
+    ClassNameMapper.mapperFromString(mapping);
 
+    // From some other proguard generated map
+    mapping = "com.moat.analytics.mobile.cha.b -> com.moat.analytics.mobile.cha.b:\n"
+        + "    com.moat.analytics.mobile.cha.MoatAdEventType[] ? -> ?\n"
+        + "    java.util.HashMap ? -> ?\n"
+        + "    java.util.HashSet ?? -> ??\n";
     ClassNameMapper.mapperFromString(mapping);
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
new file mode 100644
index 0000000..25497e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ProguardTestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class ToBeRenamedClass {
+  void toBeRenamedMethod() {
+    System.out.println("foo");
+  }
+}
+
+class WhenToApplyTestRunner {
+  public static void main(String[] args) {
+    ToBeRenamedClass instance = new ToBeRenamedClass();
+    instance.toBeRenamedMethod();
+    System.out.println(instance.getClass().getSimpleName());
+  }
+}
+
+@RunWith(Parameterized.class)
+public class WhenToApplyTest extends TestBase {
+
+  @ClassRule
+  public static TemporaryFolder temporaryFolder = ToolHelper.getTemporaryFolderForTest();
+
+  private static Class<?> MAIN = WhenToApplyTestRunner.class;
+  private static String RENAMED_CLASS_NAME =
+      ToBeRenamedClass.class.getPackage().getName() + ".ABC";
+  private static String NORMAL_OUTPUT = StringUtils.lines("foo", "ToBeRenamedClass");
+  private static String APPLIED_OUTPUT = StringUtils.lines("foo", "ABC");
+  private static String RENAMED_OUTPUT = StringUtils.lines("foo", "a");
+
+  private static Path mappingFile;
+  private static Path configuration;
+  private boolean minification;
+
+  @Parameterized.Parameters(name = "minification: {0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public WhenToApplyTest(boolean minification) {
+    this.minification = minification;
+  }
+
+  @BeforeClass
+  public static void setUpMappingFile() throws Exception {
+    mappingFile = temporaryFolder.newFile("mapping.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(mappingFile, StringUtils.lines(
+        ToBeRenamedClass.class.getTypeName() + " -> " + RENAMED_CLASS_NAME + ":",
+        "  void toBeRenamedMethod() -> abc"));
+    configuration = temporaryFolder.newFile("pg.conf").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(configuration, StringUtils.lines(
+        "-dontoptimize",
+        "-applymapping " + mappingFile
+    ));
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    ProguardTestRunResult result = testForProguard()
+        .addProgramClasses(ToBeRenamedClass.class, MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepRuleFiles(configuration)
+        .minification(minification)
+        .compile().run(MAIN);
+    if (minification) {
+      result.assertSuccessWithOutput(APPLIED_OUTPUT);
+    } else {
+      result.assertSuccessWithOutput(NORMAL_OUTPUT);
+    }
+    result.inspect(inspector -> {
+      ClassSubject classSubject = inspector.clazz(ToBeRenamedClass.class);
+      assertThat(classSubject, isPresent());
+      // As renaming won't happen again, we can use the original name to search for the method.
+      MethodSubject methodSubject = classSubject.uniqueMethodWithName("toBeRenamedMethod");
+      assertThat(methodSubject, isPresent());
+      String methodName =
+          minification
+              ? "abc"                // mapped name with minification
+              : "toBeRenamedMethod"; // original name without minification
+      assertEquals(methodName, methodSubject.getFinalName());
+    });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult result = testForR8(Backend.DEX)
+        .addProgramClasses(ToBeRenamedClass.class, MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepRuleFiles(configuration)
+        .minification(minification)
+        .compile().run(MAIN);
+    if (minification) {
+      result.assertSuccessWithOutput(RENAMED_OUTPUT);
+    } else {
+      result.assertSuccessWithOutput(APPLIED_OUTPUT);
+    }
+    result.inspect(inspector -> {
+      ClassSubject classSubject = inspector.clazz(RENAMED_CLASS_NAME);
+      assertThat(classSubject, isPresent());
+      // Mapped name will be regarded as an original name if minification is disabled.
+      String methodName =
+          minification
+              ? "toBeRenamedMethod" // original name
+              : "abc";              // mapped name without minification
+      MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
+      assertThat(methodSubject, isPresent());
+      methodName =
+          minification
+              ? "a"    // minified name
+              : "abc"; // mapped name without minification
+      assertEquals(methodName, methodSubject.getFinalName());
+    });
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
index f3746fa..fea123d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index a690bce..1609bfd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 2157cd3..99707b6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 820afc2..74a4efe 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 1dd353e..49af47a 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
index 9ef14bf..dbae1da 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index fac36d6..8430bb6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index b691046..fa76296 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.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/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index 02b61f5..d034d84 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
index 17cfb7f..044d21d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.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.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index 5f87781..49a5910 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
index 6ed0099..49a1e14 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
index 0d2a486..d2e1cd0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
index 7484255..212e883 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.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/examples/TreeShaking6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
index f5733cc..01c0de9 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
index 9d2faf3..4cbd1c0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index b957c64..5b1009e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index e64abe1..8974d6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
index a5b13a7..c204e06 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
index 825262a..4d64e0e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index d7d45f0..a373627 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
index cf7f2ad..9c2c387 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
index 1474ff8..e4b9ed6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
index 18123a3..72b6d17 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
index 819e11e..c36ddd7 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
index 967dbc5..60cbd65 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
index dc51fa1..59d8dd2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
index be844e3..15e8195 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
index fd38ee7..6f2d721 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
index a1fc427..a765f30 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
index 3ba2ce7..61b4154 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
index 8427e6a..48ef889 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.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.StringUtils;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
index e093f00..de515d0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.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.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
index 789e40c..e02b4cd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.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.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index b77b29e..2a53f1b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
index 235bb56..779c140 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
index 80573ff..7ca8334 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
index 4f04aed..0a48ba2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
index f548035..977de65 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
new file mode 100644
index 0000000..edf2b89
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnTargetedMethodTest.java
@@ -0,0 +1,68 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertThat;
+
+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 java.lang.reflect.Proxy;
+import org.junit.Test;
+
+public class IfOnTargetedMethodTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("Hello world!");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(IfOnTargetedMethodTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                "-if interface * { @" + MyAnnotation.class.getTypeName() + " <methods>; }",
+                "-keep,allowobfuscation interface <1>")
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject interfaceSubject = inspector.clazz(Interface.class);
+    assertThat(interfaceSubject, isPresent());
+    assertThat(interfaceSubject, isRenamed());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Interface obj = getInstance();
+      obj.method();
+    }
+
+    static Interface getInstance() {
+      return (Interface)
+          Proxy.newProxyInstance(
+              Interface.class.getClassLoader(),
+              new Class[] {Interface.class},
+              (o, method, objects) -> {
+                System.out.println("Hello world!");
+                return null;
+              });
+    }
+  }
+
+  interface Interface {
+
+    @MyAnnotation
+    void method();
+  }
+
+  @interface MyAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
index e8eb416..e518181 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -8,7 +8,9 @@
 @Keep
 public class KeptByFieldReflectionTest {
 
-  public final int foo = 42;
+  // TODO(b/123262024): This field must be kept un-initialized. Otherwise the "-whyareyoukeeping"
+  // output tested will hit the initialization in <init> and not the reflective access.
+  public int foo;
 
   public static void main(String[] args) throws Exception {
     // Due to b/123210548 the object cannot be created by a reflective newInstance call.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index 6feaa35..62dcebe 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -19,7 +19,6 @@
 import java.io.PrintStream;
 import java.util.Arrays;
 import java.util.Collection;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,7 +30,7 @@
   private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
   private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
 
-  private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
+  private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 0");
 
   private final String EXPECTED_WHYAREYOUKEEPING =
       StringUtils.lines(
@@ -53,7 +52,6 @@
   }
 
   @Test
-  @Ignore("b/123215165")
   public void test() throws Exception {
     MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
     FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
diff --git a/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
new file mode 100644
index 0000000..7aa573e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
@@ -0,0 +1,161 @@
+// 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.reflection;
+import static com.android.tools.r8.references.Reference.fieldFromField;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import org.junit.Assert;
+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 FieldAccessTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public FieldAccessTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  private boolean isInvokeGetField(InstructionSubject instruction) {
+    return
+        instruction.isInvoke()
+            && instruction.getMethod().qualifiedName().equals("java.lang.Class.getField");
+  }
+
+  private void runTest(Class<?> testClass) throws Exception {
+    MethodReference mainMethod =
+        methodFromMethod(testClass.getDeclaredMethod("main", String[].class));
+    FieldReference fooField = fieldFromField(testClass.getDeclaredField("foo"));
+
+    testForR8(Backend.DEX)
+        .enableGraphInspector()
+        .addProgramClasses(testClass)
+        .addKeepMainRule(testClass)
+        .run(testClass)
+        .inspectGraph(inspector -> {
+          // The only root should be the keep annotation rule.
+          assertEquals(1, inspector.getRoots().size());
+          QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+          inspector.method(mainMethod).assertNotRenamed().assertKeptBy(root);
+          inspector.field(fooField).assertRenamed();
+        })
+        .inspect(inspector -> {
+          Assert.assertTrue(
+              inspector
+                  .clazz(testClass)
+                  .uniqueMethodWithName("main")
+                  .streamInstructions()
+                  .anyMatch(this::isInvokeGetField));
+        })
+        .assertSuccessWithOutput("42");
+  }
+
+  @Test
+  public void reflectiveGet() throws Exception {
+    runTest(FieldAccessTestGet.class);
+  }
+
+  @Test
+  public void reflectiveGetStatic() throws Exception {
+    runTest(FieldAccessTestGetStatic.class);
+  }
+
+  @Test
+  public void reflectivePut() throws Exception {
+    runTest(FieldAccessTestPut.class);
+  }
+
+  @Test
+  public void reflectivePutStatic() throws Exception {
+    runTest(FieldAccessTestPutStatic.class);
+  }
+
+  @Test
+  public void reflectivePutGet() throws Exception {
+    runTest(FieldAccessTestPutGet.class);
+  }
+
+  @Test
+  public void reflectivePutGetStatic() throws Exception {
+    runTest(FieldAccessTestPutGetStatic.class);
+  }
+}
+
+class FieldAccessTestGet {
+
+  public int foo = 42;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestGet obj = new FieldAccessTestGet();
+    System.out.print(FieldAccessTestGet.class.getField("foo").getInt(obj));
+  }
+}
+
+class FieldAccessTestGetStatic {
+
+  public static int foo = 42;
+
+  public static void main(String[] args) throws Exception {
+    System.out.print(FieldAccessTestGetStatic.class.getField("foo").getInt(null));
+  }
+}
+
+class FieldAccessTestPut {
+
+  public int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPut obj = new FieldAccessTestPut();
+    FieldAccessTestPut.class.getField("foo").setInt(obj, 42);
+    System.out.print(42);
+  }
+}
+
+class FieldAccessTestPutStatic {
+
+  public static int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPutStatic.class.getField("foo").setInt(null, 42);
+    System.out.print(42);
+  }
+}
+
+class FieldAccessTestPutGet {
+
+  public int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPutGet obj = new FieldAccessTestPutGet();
+    FieldAccessTestPutGet.class.getField("foo").setInt(obj, 42);
+    System.out.print(FieldAccessTestPutGet.class.getField("foo").getInt(obj));
+  }
+}
+
+class FieldAccessTestPutGetStatic {
+
+  public static int foo;
+
+  public static void main(String[] args) throws Exception {
+    FieldAccessTestPutGetStatic.class.getField("foo").setInt(null, 42);
+    System.out.print(FieldAccessTestPutGetStatic.class.getField("foo").getInt(null));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 375f0b5..93a1c37 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -6,6 +6,7 @@
 
 
 import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -286,6 +287,11 @@
   }
 
   @Override
+  public boolean isArrayPut() {
+    return instruction instanceof CfArrayStore;
+  }
+
+  @Override
   public int size() {
     // TODO(b/122302789): CfInstruction#getSize()
     throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
new file mode 100644
index 0000000..7d5381a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.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 com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.function.Predicate;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class CodeMatchers {
+
+  public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
+    if (!targetSubject.isPresent()) {
+      throw new IllegalArgumentException();
+    }
+    DexMethod target = targetSubject.getMethod().method;
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        if (!subject.isPresent()) {
+          return false;
+        }
+        if (!subject.getMethod().hasCode()) {
+          return false;
+        }
+        return subject.streamInstructions().anyMatch(isInvokeWithTarget(target));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(" does not invoke `" + target.toSourceString() + "`");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
+  public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
+    return instruction -> instruction.isInvokeStatic() && instruction.getMethod() == target;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index e2e801e..c9b7ee5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.code.Aput;
 import com.android.tools.r8.code.CheckCast;
 import com.android.tools.r8.code.Const;
 import com.android.tools.r8.code.Const16;
@@ -366,6 +367,11 @@
   }
 
   @Override
+  public boolean isArrayPut() {
+    return instruction instanceof Aput;
+  }
+
+  @Override
   public boolean isMonitorEnter() {
     return instruction instanceof MonitorEnter;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index dd5af7c..0528185 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -84,6 +84,8 @@
 
   boolean isNewArray();
 
+  boolean isArrayPut();
+
   boolean isMonitorEnter();
 
   boolean isMonitorExit();
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index 6013e6f..be380ed 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -8,7 +8,6 @@
 import optparse
 import os
 import shutil
-import subprocess
 import sys
 import utils
 
@@ -32,6 +31,9 @@
   parser.add_option('--adb-options',
                     help='additional adb options when running adb',
                     default=None)
+  parser.add_option('--quiet',
+                    help='disable verbose logging',
+                    default=False)
   (options, args) = parser.parse_args()
   if len(args) != 1:
     parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -41,44 +43,53 @@
 def findKeystore():
   return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
 
-def repack(processed_out, original_apk, temp):
+def repack(processed_out, original_apk, temp, quiet):
   processed_apk = os.path.join(temp, 'processed.apk')
   shutil.copyfile(original_apk, processed_apk)
   if not processed_out:
-    print 'Using original APK as is'
+    utils.Print('Using original APK as is', quiet=quiet)
     return processed_apk
-  print 'Repacking APK with dex files from', processed_apk
-  with utils.ChangedWorkingDirectory(temp):
+  utils.Print(
+      'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
+  with utils.ChangedWorkingDirectory(temp, quiet=quiet):
     cmd = ['zip', '-d', 'processed.apk', '*.dex']
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    utils.RunCmd(cmd, quiet=quiet)
   if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
     cmd = ['unzip', processed_out, '-d', temp]
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    if quiet:
+      cmd.insert(1, '-q')
+    utils.RunCmd(cmd, quiet=quiet)
     processed_out = temp
-  with utils.ChangedWorkingDirectory(processed_out):
+  with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
     dex = glob.glob('*.dex')
     cmd = ['zip', '-u', '-9', processed_apk] + dex
-    utils.PrintCmd(cmd)
-    subprocess.check_call(cmd)
+    utils.RunCmd(cmd, quiet=quiet)
   return processed_apk
 
-def sign(unsigned_apk, keystore, temp):
+def sign(unsigned_apk, keystore, temp, quiet):
   signed_apk = os.path.join(temp, 'unaligned.apk')
-  apk_utils.sign(unsigned_apk, signed_apk, keystore)
+  apk_utils.sign(unsigned_apk, signed_apk, keystore, quiet=quiet)
   return signed_apk
 
-def align(signed_apk, temp):
-  print 'Aligning'
+def align(signed_apk, temp, quiet):
+  utils.Print('Aligning', quiet=quiet)
   aligned_apk = os.path.join(temp, 'aligned.apk')
-  cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
-  print ' '.join(cmd)
-  subprocess.check_call(cmd)
+  zipalign_path = (
+      'zipalign' if 'build_tools' in os.environ.get('PATH')
+      else os.path.join(utils.ANDROID_BUILD_TOOLS, 'zipalign'))
+  cmd = [
+    zipalign_path,
+    '-f',
+    '4',
+    signed_apk,
+    aligned_apk
+  ]
+  utils.RunCmd(cmd, quiet=quiet)
   return signed_apk
 
 def masseur(
-    apk, dex=None, out=None, adb_options=None, keystore=None, install=False):
+    apk, dex=None, out=None, adb_options=None, keystore=None, install=False,
+    quiet=False):
   if not out:
     out = os.path.basename(apk)
   if not keystore:
@@ -86,23 +97,23 @@
   with utils.TempDir() as temp:
     processed_apk = None
     if dex:
-      processed_apk = repack(dex, apk, temp)
+      processed_apk = repack(dex, apk, temp, quiet)
     else:
-      print 'Signing original APK without modifying dex files'
+      utils.Print(
+          'Signing original APK without modifying dex files', quiet=quiet)
       processed_apk = os.path.join(temp, 'processed.apk')
       shutil.copyfile(apk, processed_apk)
-    signed_apk = sign(processed_apk, keystore, temp)
-    aligned_apk = align(signed_apk, temp)
-    print 'Writing result to', out
+    signed_apk = sign(processed_apk, keystore, temp, quiet=quiet)
+    aligned_apk = align(signed_apk, temp, quiet=quiet)
+    utils.Print('Writing result to {}'.format(out), quiet=quiet)
     shutil.copyfile(aligned_apk, out)
-    adb_cmd = ['adb']
-    if adb_options:
-      adb_cmd.extend(
-          [option for option in adb_options.split(' ') if option])
     if install:
+      adb_cmd = ['adb']
+      if adb_options:
+        adb_cmd.extend(
+            [option for option in adb_options.split(' ') if option])
       adb_cmd.extend(['install', '-t', '-r', '-d', out]);
-      utils.PrintCmd(adb_cmd)
-      subprocess.check_call(adb_cmd)
+      utils.RunCmd(adb_cmd, quiet=quiet)
 
 def main():
   (options, apk) = parse_options()
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index aaae1e4..5a6aa94 100644
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -7,11 +7,10 @@
 import subprocess
 import utils
 
-def sign(unsigned_apk, signed_apk, keystore):
-  print 'Signing (ignore the warnings)'
+def sign(unsigned_apk, signed_apk, keystore, quiet=False):
+  utils.Print('Signing (ignore the warnings)', quiet=quiet)
   cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
-  utils.PrintCmd(cmd)
-  subprocess.call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
   cmd = [
     'jarsigner',
     '-sigalg', 'SHA1withRSA',
@@ -22,8 +21,7 @@
     unsigned_apk,
     'androiddebugkey'
   ]
-  utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
 
 def sign_with_apksigner(build_tools_dir, unsigned_apk, signed_apk, keystore, password):
   cmd = [
diff --git a/tools/as_utils.py b/tools/as_utils.py
index b4eb396..10fb29f 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -10,9 +10,10 @@
 
 import utils
 
-def add_r8_dependency(checkout_dir, minified):
+def add_r8_dependency(checkout_dir, temp_dir, minified):
   build_file = os.path.join(checkout_dir, 'build.gradle')
-  assert os.path.isfile(build_file), 'Expected a file to be present at {}'.format(build_file)
+  assert os.path.isfile(build_file), (
+      'Expected a file to be present at {}'.format(build_file))
 
   with open(build_file) as f:
     lines = f.readlines()
@@ -25,26 +26,19 @@
     for line in lines:
       stripped = line.strip()
       if stripped == 'dependencies {':
-        assert not is_inside_dependencies, 'Unexpected line with \'dependencies {\''
+        assert not is_inside_dependencies, (
+            'Unexpected line with \'dependencies {\'')
         is_inside_dependencies = True
       if is_inside_dependencies:
-        if utils.R8_JAR in stripped:
-          if minified:
-            # Skip line to avoid dependency on r8.jar
-            continue
-          added_r8_dependency = True
-        elif utils.R8LIB_JAR in stripped:
-          if not minified:
-            # Skip line to avoid dependency on r8lib.jar
-            continue
-          added_r8_dependency = True
+        if '/r8.jar' in stripped or '/r8lib.jar' in stripped:
+          # Skip line to avoid dependency on r8.jar
+          continue
         elif 'com.android.tools.build:gradle:' in stripped:
           gradle_version = stripped[stripped.rindex(':')+1:-1]
-          if not added_r8_dependency:
-            indent = ''.ljust(line.index('classpath'))
-            jar = utils.R8LIB_JAR if minified else utils.R8_JAR
-            f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
-            added_r8_dependency = True
+          indent = ''.ljust(line.index('classpath'))
+          jar = os.path.join(temp_dir, 'r8lib.jar' if minified else 'r8.jar')
+          f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
+          added_r8_dependency = True
         elif stripped == '}':
           is_inside_dependencies = False
       f.write(line)
@@ -63,45 +57,38 @@
     lines = f.readlines()
   with open(build_file, 'w') as f:
     for line in lines:
-      if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
+      if ('/r8.jar' not in line) and ('/r8lib.jar' not in line):
         f.write(line)
 
 def GetMinAndCompileSdk(app, config, checkout_dir, apk_reference):
-  app_module = config.get('app_module', 'app')
-  build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle')
-  assert os.path.isfile(build_gradle_file), (
-      'Expected to find build.gradle file at {}'.format(build_gradle_file))
 
-  compile_sdk = None
-  min_sdks = []
-  target_sdk = None
+  compile_sdk = config.get('compile_sdk', None)
+  min_sdk = config.get('min_sdk', None)
 
-  with open(build_gradle_file) as f:
-    for line in f.readlines():
-      stripped = line.strip()
-      if stripped.startswith('compileSdkVersion '):
-        assert not compile_sdk
-        compile_sdk = int(stripped[len('compileSdkVersion '):])
-      if stripped.startswith('minSdkVersion '):
-        min_sdks.append(int(stripped[len('minSdkVersion '):]))
-      elif stripped.startswith('targetSdkVersion '):
-        assert not target_sdk
-        target_sdk = int(stripped[len('targetSdkVersion '):])
+  if not compile_sdk or not min_sdk:
+    app_module = config.get('app_module', 'app')
+    build_gradle_file = os.path.join(checkout_dir, app_module, 'build.gradle')
+    assert os.path.isfile(build_gradle_file), (
+        'Expected to find build.gradle file at {}'.format(build_gradle_file))
 
-  if len(min_sdks) == 1:
-    min_sdk = min_sdks[0]
-  else:
-    assert 'min_sdk' in config
-    min_sdk = config.get('min_sdk')
+    # Attempt to find the sdk values from build.gradle.
+    with open(build_gradle_file) as f:
+      for line in f.readlines():
+        stripped = line.strip()
+        if stripped.startswith('compileSdkVersion '):
+          if 'compile_sdk' not in config:
+            assert not compile_sdk
+            compile_sdk = int(stripped[len('compileSdkVersion '):])
+        elif stripped.startswith('minSdkVersion '):
+          if 'min_sdk' not in config:
+            assert not min_sdk
+            min_sdk = int(stripped[len('minSdkVersion '):])
 
   assert min_sdk, (
       'Expected to find `minSdkVersion` in {}'.format(build_gradle_file))
   assert compile_sdk, (
       'Expected to find `compileSdkVersion` in {}'.format(build_gradle_file))
 
-  assert not target_sdk or target_sdk == compile_sdk, (
-      'Expected `compileSdkVersion` and `targetSdkVersion` to be the same')
-
   return (min_sdk, compile_sdk)
 
 def IsGradleTaskName(x):
@@ -152,8 +139,9 @@
   # one of the predefined locations.
   assert False
 
-def Move(src, dst):
-  print('Moving `{}` to `{}`'.format(src, dst))
+def Move(src, dst, quiet=False):
+  if not quiet:
+    print('Moving `{}` to `{}`'.format(src, dst))
   dst_parent = os.path.dirname(dst)
   if not os.path.isdir(dst_parent):
     os.makedirs(dst_parent)
@@ -163,15 +151,15 @@
     os.remove(dst)
   os.rename(src, dst)
 
-def MoveDir(src, dst):
+def MoveDir(src, dst, quiet=False):
   assert os.path.isdir(src)
-  Move(src, dst)
+  Move(src, dst, quiet=quiet)
 
-def MoveFile(src, dst):
+def MoveFile(src, dst, quiet=False):
   assert os.path.isfile(src)
-  Move(src, dst)
+  Move(src, dst, quiet=quiet)
 
-def MoveProfileReportTo(dest_dir, build_stdout):
+def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
   html_file = None
   profile_message = 'See the profiling report at: '
   for line in build_stdout:
@@ -186,11 +174,12 @@
 
   assert os.path.isfile(html_file), 'Expected to find HTML file at {}'.format(
       html_file)
-  MoveFile(html_file, os.path.join(dest_dir, 'index.html'))
+  MoveFile(html_file, os.path.join(dest_dir, 'index.html'), quiet=quiet)
 
   html_dir = os.path.dirname(html_file)
   for dir_name in ['css', 'js']:
-    MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name))
+    MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name),
+        quiet=quiet)
 
 def ParseProfileReport(profile_dir):
   html_file = os.path.join(profile_dir, 'index.html')
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 5bf1da2..a9a90eb 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -235,6 +235,10 @@
     if get_magic_file_exists(READY_FOR_TESTING):
       git_hash = get_magic_file_content(READY_FOR_TESTING)
       checked_out = git_checkout(git_hash)
+      # If the script changed, we need to restart now to get correct commands
+      # Note that we have not removed the READY_FOR_TESTING yet, so if we
+      # execv we will pick up the same version.
+      restart_if_new_version(own_content)
       # Sanity check, if this does not succeed stop.
       if checked_out != git_hash:
         log('Inconsistent state: %s %s' % (git_hash, checked_out))
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
index ad26959..9f2f7e4 100755
--- a/tools/run_bootstrap_benchmark.py
+++ b/tools/run_bootstrap_benchmark.py
@@ -60,11 +60,11 @@
       sys.exit(return_code)
 
     dex(r8_output, d8_r8_output)
-    print "BootstrapR8(CodeSize):", os.path.getsize(r8_output)
-    print "BootstrapR8Dex(CodeSize):", os.path.getsize(d8_r8_output)
+    print "BootstrapR8(CodeSize):", utils.uncompressed_size(r8_output)
+    print "BootstrapR8Dex(CodeSize):", utils.uncompressed_size(d8_r8_output)
 
     dex(PINNED_PGR8_JAR, d8_pg_output)
-    print "BootstrapR8PG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
-    print "BootstrapR8PGDex(CodeSize):", os.path.getsize(d8_pg_output)
+    print "BootstrapR8PG(CodeSize):", utils.uncompressed_size(PINNED_PGR8_JAR)
+    print "BootstrapR8PGDex(CodeSize):", utils.uncompressed_size(d8_pg_output)
 
   sys.exit(0)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index e9b2dcd..aacfbe9 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -8,9 +8,9 @@
 import gradle
 import os
 import optparse
+import shutil
 import subprocess
 import sys
-import tempfile
 import time
 import utils
 import zipfile
@@ -36,11 +36,14 @@
       'git_repo': 'https://github.com/christofferqa/AnExplorer',
       'flavor': 'googleMobilePro',
       'signed-apk-name': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
+      'min_sdk': 17
   },
   'AntennaPod': {
       'app_id': 'de.danoeh.antennapod',
       'git_repo': 'https://github.com/christofferqa/AntennaPod.git',
       'flavor': 'play',
+      'min_sdk': 14,
+      'compile_sdk': 26
   },
   'apps-android-wikipedia': {
       'app_id': 'org.wikipedia',
@@ -48,6 +51,10 @@
       'flavor': 'prod',
       'signed-apk-name': 'app-prod-universal-release.apk'
   },
+  'chanu': {
+    'app_id': 'com.chanapps.four.activity',
+    'git_repo': 'https://github.com/mkj-gram/chanu.git',
+  },
   'friendlyeats-android': {
       'app_id': 'com.google.firebase.example.fireeats',
       'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git'
@@ -68,6 +75,19 @@
       'app_id': 'org.schabi.newpipe',
       'git_repo': 'https://github.com/christofferqa/NewPipe',
   },
+  'rover-android': {
+    'app_id': 'io.rover.app.debug',
+    'app_module': 'debug-app',
+    'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+  },
+  'Signal-Android': {
+    'app_id': 'org.thoughtcrime.securesms',
+    'app_module': '',
+    'flavor': 'play',
+    'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
+    'releaseTarget': 'assemblePlayRelease',
+    'signed-apk-name': 'Signal-play-release-4.32.7.apk',
+  },
   'Simple-Calendar': {
       'app_id': 'com.simplemobiletools.calendar.pro',
       'git_repo': 'https://github.com/christofferqa/Simple-Calendar',
@@ -88,17 +108,10 @@
       # TODO(123047413): Fails with R8.
       'skip': True,
   },
-  'Signal-Android': {
-    'app_id': 'org.thoughtcrime.securesms',
-    'app_module': '',
-    'flavor': 'play',
-    'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
-    'releaseTarget': 'assemblePlayRelease',
-    'signed-apk-name': 'Signal-play-release-4.32.7.apk',
-  },
-  'chanu': {
-    'app_id': 'com.chanapps.four.activity',
-    'git_repo': 'https://github.com/mkj-gram/chanu.git',
+  'Tusky': {
+    'app_id': 'com.keylesspalace.tusky',
+    'git_repo': 'https://github.com/mkj-gram/Tusky.git',
+    'flavor': 'blue'
   },
   # This does not build yet.
   'muzei': {
@@ -109,13 +122,6 @@
   },
 }
 
-# Common environment setup.
-user_home = os.path.expanduser('~')
-android_home = os.path.join(user_home, 'Android', 'Sdk')
-android_build_tools_version = '28.0.3'
-android_build_tools = os.path.join(
-    android_home, 'build-tools', android_build_tools_version)
-
 # TODO(christofferqa): Do not rely on 'emulator-5554' name
 emulator_id = 'emulator-5554'
 
@@ -127,9 +133,18 @@
       dex_size += z.getinfo(filename).file_size
   return dex_size
 
-def IsBuiltWithR8(apk):
-  script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
-  return '~~R8' in subprocess.check_output(['python', script, apk]).strip()
+def IsBuiltWithR8(apk, temp_dir, options):
+  r8_jar = os.path.join(temp_dir, 'r8.jar')
+
+  # Use the copy of r8.jar if it is there.
+  if os.path.isfile(r8_jar):
+    cmd = ['java', '-ea', '-jar', r8_jar, 'extractmarker', apk]
+  else:
+    script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
+    cmd = ['python', script, apk]
+
+  utils.PrintCmd(cmd, quiet=options.quiet)
+  return '~~R8' in subprocess.check_output(cmd).strip()
 
 def IsMinifiedR8(shrinker):
   return shrinker == 'r8-minified' or shrinker == 'r8full-minified'
@@ -147,9 +162,13 @@
 def GitCheckout(file):
   return subprocess.check_output(['git', 'checkout', file]).strip()
 
-def InstallApkOnEmulator(apk_dest):
-  subprocess.check_call(
-      ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+def InstallApkOnEmulator(apk_dest, options):
+  cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest]
+  if options.quiet:
+    with open(os.devnull, 'w') as devnull:
+      subprocess.check_call(cmd, stdout=devnull)
+  else:
+    subprocess.check_call(cmd)
 
 def PercentageDiffAsString(before, after):
   if after < before:
@@ -157,7 +176,7 @@
   else:
     return '+' + str(round((after - before) / before * 100)) + '%'
 
-def UninstallApkOnEmulator(app, config):
+def UninstallApkOnEmulator(app, config, options):
   app_id = config.get('app_id')
   process = subprocess.Popen(
       ['adb', 'uninstall', app_id],
@@ -196,7 +215,7 @@
     else:
       return True
 
-def GetResultsForApp(app, config, options):
+def GetResultsForApp(app, config, options, temp_dir):
   git_repo = config['git_repo']
 
   # Checkout and build in the build directory.
@@ -205,10 +224,10 @@
   result = {}
 
   if not os.path.exists(checkout_dir):
-    with utils.ChangedWorkingDirectory(WORKING_DIR):
+    with utils.ChangedWorkingDirectory(WORKING_DIR, quiet=options.quiet):
       GitClone(git_repo)
   elif options.pull:
-    with utils.ChangedWorkingDirectory(checkout_dir):
+    with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
       # Checkout build.gradle to avoid merge conflicts.
       if IsTrackedByGit('build.gradle'):
         GitCheckout('build.gradle')
@@ -221,16 +240,16 @@
   result['status'] = 'success'
 
   result_per_shrinker = BuildAppWithSelectedShrinkers(
-      app, config, options, checkout_dir)
+      app, config, options, checkout_dir, temp_dir)
   for shrinker, shrinker_result in result_per_shrinker.iteritems():
     result[shrinker] = shrinker_result
 
   return result
 
-def BuildAppWithSelectedShrinkers(app, config, options, checkout_dir):
+def BuildAppWithSelectedShrinkers(app, config, options, checkout_dir, temp_dir):
   result_per_shrinker = {}
 
-  with utils.ChangedWorkingDirectory(checkout_dir):
+  with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
     for shrinker in SHRINKERS:
       if options.shrinker and shrinker not in options.shrinker:
         continue
@@ -242,7 +261,7 @@
         out_dir = os.path.join(checkout_dir, 'out', shrinker)
         (apk_dest, profile_dest_dir, proguard_config_file) = \
             BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
-                options)
+                temp_dir, options)
         dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
         result['apk_dest'] = apk_dest,
         result['build_status'] = 'success'
@@ -270,15 +289,9 @@
           # Build app with gradle using -D...keepRuleSynthesisForRecompilation=
           # true.
           out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
-          extra_env_vars = {
-            'JAVA_OPTS': ' '.join([
-              '-ea:com.android.tools.r8...',
-              '-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true'
-            ])
-          }
           (apk_dest, profile_dest_dir, ext_proguard_config_file) = \
               BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
-                  options, extra_env_vars)
+                  temp_dir, options, keepRuleSynthesisForRecompilation=True)
           dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
           recompilation_result = {
             'apk_dest': apk_dest,
@@ -304,8 +317,9 @@
               recompiled_apk_dest = os.path.join(
                   checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
               RebuildAppWithShrinker(
-                  previous_apk, recompiled_apk_dest, ext_proguard_config_file,
-                  shrinker, min_sdk, compile_sdk)
+                  app, previous_apk, recompiled_apk_dest,
+                  ext_proguard_config_file, shrinker, min_sdk, compile_sdk,
+                  options, temp_dir)
               recompilation_result = {
                 'apk_dest': recompiled_apk_dest,
                 'build_status': 'success',
@@ -324,18 +338,26 @@
 
       result_per_shrinker[shrinker] = result
 
+  if not options.app:
+    print('')
+    LogResultsForApp(app, result_per_shrinker, options)
+    print('')
+
   return result_per_shrinker
 
 def BuildAppWithShrinker(
-    app, config, shrinker, checkout_dir, out_dir, options, env_vars=None):
-  print()
-  print('Building {} with {}'.format(app, shrinker))
+    app, config, shrinker, checkout_dir, out_dir, temp_dir, options,
+    keepRuleSynthesisForRecompilation=False):
+  print('Building {} with {}{}'.format(
+      app,
+      shrinker,
+      ' for recompilation' if keepRuleSynthesisForRecompilation else ''))
 
   # Add/remove 'r8.jar' from top-level build.gradle.
   if options.disable_tot:
     as_utils.remove_r8_dependency(checkout_dir)
   else:
-    as_utils.add_r8_dependency(checkout_dir, IsMinifiedR8(shrinker))
+    as_utils.add_r8_dependency(checkout_dir, temp_dir, IsMinifiedR8(shrinker))
 
   app_module = config.get('app_module', 'app')
   archives_base_name = config.get('archives_base_name', app_module)
@@ -350,11 +372,9 @@
   as_utils.SetPrintConfigurationDirective(
       app, config, checkout_dir, proguard_config_dest)
 
-  env = os.environ.copy()
-  env['ANDROID_HOME'] = android_home
+  env = {}
+  env['ANDROID_HOME'] = utils.ANDROID_HOME
   env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
-  if env_vars:
-    env.update(env_vars)
 
   releaseTarget = config.get('releaseTarget')
   if not releaseTarget:
@@ -370,20 +390,12 @@
          '--profile', '--stacktrace',
          '-Pandroid.enableR8=' + str(enableR8).lower(),
          '-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+  if keepRuleSynthesisForRecompilation:
+    cmd.append('-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
   if options.gradle_flags:
     cmd.extend(options.gradle_flags.split(' '))
 
-  utils.PrintCmd(cmd)
-  build_process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
-  stdout = []
-  while True:
-    line = build_process.stdout.readline()
-    if line != b'':
-      stripped = line.rstrip()
-      stdout.append(stripped)
-      print(stripped)
-    else:
-      break
+  stdout = utils.RunCmd(cmd, env, quiet=options.quiet)
 
   apk_base_name = (archives_base_name
       + (('-' + flavor) if flavor else '') + '-release')
@@ -406,7 +418,7 @@
       keystore = 'app.keystore'
       keystore_password = 'android'
       apk_utils.sign_with_apksigner(
-          android_build_tools,
+          utils.ANDROID_BUILD_TOOLS,
           unsigned_apk,
           signed_apk,
           keystore,
@@ -414,29 +426,31 @@
 
   if os.path.isfile(signed_apk):
     apk_dest = os.path.join(out_dir, signed_apk_name)
-    as_utils.MoveFile(signed_apk, apk_dest)
+    as_utils.MoveFile(signed_apk, apk_dest, quiet=options.quiet)
   else:
     apk_dest = os.path.join(out_dir, unsigned_apk_name)
-    as_utils.MoveFile(unsigned_apk, apk_dest)
+    as_utils.MoveFile(unsigned_apk, apk_dest, quiet=options.quiet)
 
-  assert IsBuiltWithR8(apk_dest) == ('r8' in shrinker), (
+  assert IsBuiltWithR8(apk_dest, temp_dir, options) == ('r8' in shrinker), (
       'Unexpected marker in generated APK for {}'.format(shrinker))
 
   profile_dest_dir = os.path.join(out_dir, 'profile')
-  as_utils.MoveProfileReportTo(profile_dest_dir, stdout)
+  as_utils.MoveProfileReportTo(profile_dest_dir, stdout, quiet=options.quiet)
 
   return (apk_dest, profile_dest_dir, proguard_config_dest)
 
 def RebuildAppWithShrinker(
-    apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk):
+    app, apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
+    options, temp_dir):
   assert 'r8' in shrinker
   assert apk_dest.endswith('.apk')
 
+  print('Rebuilding {} with {}'.format(app, shrinker))
+
   # Compile given APK with shrinker to temporary zip file.
-  android_jar = os.path.join(
-      utils.REPO_ROOT,
-      utils.ANDROID_JAR.format(api=compile_sdk))
-  r8_jar = utils.R8LIB_JAR if IsMinifiedR8(shrinker) else utils.R8_JAR
+  android_jar = utils.get_android_jar(compile_sdk)
+  r8_jar = os.path.join(
+      temp_dir, 'r8lib.jar' if IsMinifiedR8(shrinker) else 'r8.jar')
   zip_dest = apk_dest[:-4] + '.zip'
 
   # TODO(christofferqa): Entry point should be CompatProguard if the shrinker
@@ -446,20 +460,24 @@
   cmd = ['java', '-ea:com.android.tools.r8...', '-cp', r8_jar, entry_point,
       '--release', '--min-api', str(min_sdk), '--pg-conf', proguard_config_file,
       '--lib', android_jar, '--output', zip_dest, apk]
-  utils.PrintCmd(cmd)
 
-  subprocess.check_output(cmd)
+  for android_optional_jar in utils.get_android_optional_jars(compile_sdk):
+    cmd.append('--lib')
+    cmd.append(android_optional_jar)
+
+  utils.RunCmd(cmd, quiet=options.quiet)
 
   # Make a copy of the given APK, move the newly generated dex files into the
   # copied APK, and then sign the APK.
-  apk_masseur.masseur(apk, dex=zip_dest, out=apk_dest)
+  apk_masseur.masseur(
+    apk, dex=zip_dest, out=apk_dest, quiet=options.quiet)
 
 def RunMonkey(app, config, options, apk_dest):
   if not WaitForEmulator():
     return False
 
-  UninstallApkOnEmulator(app, config)
-  InstallApkOnEmulator(apk_dest)
+  UninstallApkOnEmulator(app, config, options)
+  InstallApkOnEmulator(apk_dest, options)
 
   app_id = config.get('app_id')
   number_of_events_to_generate = options.monkey_events
@@ -470,27 +488,28 @@
 
   cmd = ['adb', 'shell', 'monkey', '-p', app_id, '-s', str(random_seed),
       str(number_of_events_to_generate)]
-  utils.PrintCmd(cmd)
 
   try:
-    stdout = subprocess.check_output(cmd)
+    stdout = utils.RunCmd(cmd, quiet=options.quiet)
     succeeded = (
         'Events injected: {}'.format(number_of_events_to_generate) in stdout)
   except subprocess.CalledProcessError as e:
     succeeded = False
 
-  UninstallApkOnEmulator(app, config)
+  UninstallApkOnEmulator(app, config, options)
 
   return succeeded
 
 def LogResultsForApps(result_per_shrinker_per_app, options):
-  for app, result_per_shrinker in result_per_shrinker_per_app.iteritems():
+  print('')
+  for app, result_per_shrinker in sorted(
+      result_per_shrinker_per_app.iteritems()):
     LogResultsForApp(app, result_per_shrinker, options)
 
 def LogResultsForApp(app, result_per_shrinker, options):
   print(app + ':')
 
-  if result_per_shrinker.get('status') != 'success':
+  if result_per_shrinker.get('status', 'success') != 'success':
     error_message = result_per_shrinker.get('error_message')
     print('  skipped ({})'.format(error_message))
     return
@@ -597,6 +616,10 @@
                     action='store_true')
   result.add_option('--gradle-flags', '--gradle_flags',
                     help='Flags to pass in to gradle')
+  result.add_option('--quiet',
+                    help='Disable verbose logging',
+                    default=False,
+                    action='store_true')
   (options, args) = result.parse_args(argv)
   if options.disable_tot:
     # r8.jar is required for recompiling the generated APK
@@ -610,32 +633,39 @@
   global SHRINKERS
 
   (options, args) = ParseOptions(argv)
-  assert options.disable_tot or os.path.isfile(utils.R8_JAR), (
-      'Cannot build from ToT without r8.jar')
-  assert options.disable_tot or os.path.isfile(utils.R8LIB_JAR), (
-      'Cannot build from ToT without r8lib.jar')
 
-  if options.disable_tot:
-    # Cannot run r8 lib without adding r8lib.jar as an dependency
-    SHRINKERS = [
-        shrinker for shrinker in SHRINKERS
-        if 'minified' not in shrinker]
+  with utils.TempDir() as temp_dir:
+    if options.disable_tot:
+      # Cannot run r8 lib without adding r8lib.jar as an dependency
+      SHRINKERS = [
+          shrinker for shrinker in SHRINKERS
+          if 'minified' not in shrinker]
+    else:
+      if not options.no_build:
+        gradle.RunGradle(['r8', 'r8lib'])
 
-  if not options.no_build and not options.disable_tot:
-    gradle.RunGradle(['r8', 'r8lib'])
+      assert os.path.isfile(utils.R8_JAR), (
+          'Cannot build from ToT without r8.jar')
+      assert os.path.isfile(utils.R8LIB_JAR), (
+          'Cannot build from ToT without r8lib.jar')
 
-  result_per_shrinker_per_app = {}
+      # Make a copy of r8.jar and r8lib.jar such that they stay the same for
+      # the entire execution of this script.
+      shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
+      shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
 
-  if options.app:
-    result_per_shrinker_per_app[options.app] = GetResultsForApp(
-        options.app, APPS.get(options.app), options)
-  else:
-    for app, config in APPS.iteritems():
-      if not config.get('skip', False):
-        result_per_shrinker_per_app[app] = GetResultsForApp(
-            app, config, options)
+    result_per_shrinker_per_app = {}
 
-  LogResultsForApps(result_per_shrinker_per_app, options)
+    if options.app:
+      result_per_shrinker_per_app[options.app] = GetResultsForApp(
+          options.app, APPS.get(options.app), options, temp_dir)
+    else:
+      for app, config in sorted(APPS.iteritems()):
+        if not config.get('skip', False):
+          result_per_shrinker_per_app[app] = GetResultsForApp(
+              app, config, options, temp_dir)
+
+    LogResultsForApps(result_per_shrinker_per_app, options)
 
 def success(message):
   CGREEN = '\033[32m'
diff --git a/tools/utils.py b/tools/utils.py
index f8b4469..ad85089 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -12,8 +12,10 @@
 import sys
 import tarfile
 import tempfile
+import zipfile
 
-ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
+ANDROID_JAR_DIR = 'third_party/android_jar/lib-v{api}'
+ANDROID_JAR = os.path.join(ANDROID_JAR_DIR, 'android.jar')
 TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
 REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..'))
 THIRD_PARTY = os.path.join(REPO_ROOT, 'third_party')
@@ -53,13 +55,93 @@
 R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
 RETRACE_JAR = os.path.join(THIRD_PARTY, 'proguard', 'proguard6.0.1', 'lib', 'retrace.jar')
 
-def PrintCmd(s):
-  if type(s) is list:
-    s = ' '.join(s)
-  print 'Running: %s' % s
+# Common environment setup.
+USER_HOME = os.path.expanduser('~')
+ANDROID_HOME = os.path.join(USER_HOME, 'Android', 'Sdk')
+ANDROID_BUILD_TOOLS_VERSION = '28.0.3'
+ANDROID_BUILD_TOOLS = os.path.join(
+    ANDROID_HOME, 'build-tools', ANDROID_BUILD_TOOLS_VERSION)
+
+def Print(s, quiet=False):
+  if quiet:
+    return
+  print(s)
+
+def Warn(message):
+  CRED = '\033[91m'
+  CEND = '\033[0m'
+  print(CRED + message + CEND)
+
+def PrintCmd(cmd, env=None, quiet=False):
+  if quiet:
+    return
+  if type(cmd) is list:
+    cmd = ' '.join(cmd)
+  if env:
+    env = ' '.join(['{}=\"{}\"'.format(x, y) for x, y in env.iteritems()])
+    print('Running: {} {}'.format(env, cmd))
+  else:
+    print('Running: {}'.format(cmd))
   # I know this will hit os on windows eventually if we don't do this.
   sys.stdout.flush()
 
+class ProgressLogger(object):
+  CLEAR_LINE = '\033[K'
+  UP = '\033[F'
+
+  def __init__(self, quiet=False):
+    self._count = 0
+    self._has_printed = False
+    self._quiet = quiet
+
+  def log(self, text):
+    if self._quiet:
+      if self._has_printed:
+        sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+      if len(text) > 140:
+        text = text[0:140] + '...'
+    print(text)
+    self._has_printed = True
+
+  def done(self):
+    if self._quiet and self._has_printed:
+      sys.stdout.write(ProgressLogger.UP + ProgressLogger.CLEAR_LINE)
+      print('')
+      sys.stdout.write(ProgressLogger.UP)
+
+def RunCmd(cmd, env_vars=None, quiet=False):
+  PrintCmd(cmd, env=env_vars, quiet=quiet)
+  env = os.environ.copy()
+  if env_vars:
+    env.update(env_vars)
+  process = subprocess.Popen(
+      cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+  stdout = []
+  logger = ProgressLogger(quiet=quiet)
+  failed = False
+  while True:
+    line = process.stdout.readline()
+    if line != b'':
+      stripped = line.rstrip()
+      stdout.append(stripped)
+      logger.log(stripped)
+
+      # TODO(christofferqa): r8 should fail with non-zero exit code.
+      if ('AssertionError:' in stripped
+          or 'CompilationError:' in stripped
+          or 'CompilationFailedException:' in stripped
+          or 'Compilation failed' in stripped):
+        failed = True
+    else:
+      logger.done()
+      exit_code = process.poll()
+      if exit_code or failed:
+        for line in stdout:
+          Warn(line)
+        raise subprocess.CalledProcessError(
+            exit_code, cmd, output='\n'.join(stdout))
+      return stdout
+
 def IsWindows():
   return os.name == 'nt'
 
@@ -195,16 +277,19 @@
    shutil.rmtree(self._temp_dir, ignore_errors=True)
 
 class ChangedWorkingDirectory(object):
- def __init__(self, working_directory):
+ def __init__(self, working_directory, quiet=False):
+   self._quiet = quiet
    self._working_directory = working_directory
 
  def __enter__(self):
    self._old_cwd = os.getcwd()
-   print 'Enter directory = ', self._working_directory
+   if not self._quiet:
+     print 'Enter directory:', self._working_directory
    os.chdir(self._working_directory)
 
  def __exit__(self, *_):
-   print 'Enter directory = ', self._old_cwd
+   if not self._quiet:
+     print 'Enter directory:', self._old_cwd
    os.chdir(self._old_cwd)
 
 # Reading Android CTS test_result.xml
@@ -308,8 +393,29 @@
   if m is not None:
     raise Exception("Do not use google JVM for benchmarking: " + version)
 
+def get_android_jar_dir(api):
+  return os.path.join(REPO_ROOT, ANDROID_JAR_DIR.format(api=api))
+
 def get_android_jar(api):
   return os.path.join(REPO_ROOT, ANDROID_JAR.format(api=api))
 
+def get_android_optional_jars(api):
+  android_optional_jars_dir = os.path.join(get_android_jar_dir(api), 'optional')
+  android_optional_jars = [
+    os.path.join(android_optional_jars_dir, 'android.test.base.jar'),
+    os.path.join(android_optional_jars_dir, 'android.test.mock.jar'),
+    os.path.join(android_optional_jars_dir, 'android.test.runner.jar'),
+    os.path.join(android_optional_jars_dir, 'org.apache.http.legacy.jar')
+  ]
+  return [
+      android_optional_jar for android_optional_jar in android_optional_jars
+      if os.path.isfile(android_optional_jar)]
+
 def is_bot():
   return 'BUILDBOT_BUILDERNAME' in os.environ
+
+
+def uncompressed_size(path):
+  return sum(z.file_size for z in zipfile.ZipFile(path).infolist())
+
+