Merge "Reproduce b/112452064."
diff --git a/src/main/java/com/android/tools/r8/ByteBufferProvider.java b/src/main/java/com/android/tools/r8/ByteBufferProvider.java
new file mode 100644
index 0000000..cf80266
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ByteBufferProvider.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.nio.ByteBuffer;
+
+/** Interface to enable manual memory management for a pool of byte buffers. */
+@KeepForSubclassing
+public interface ByteBufferProvider {
+
+  /**
+   * Acquire unique ownership of a {@link ByteBuffer}.
+   *
+   * <p>The buffer must have an array backing and must have capacity of at least the requested
+   * {@param capacity} value. The buffer must be positioned at position zero.
+   *
+   * <p>The buffer is owned by the caller until it is explicitly given back by a call to {@link
+   * ByteBufferProvider::releaseByteBuffer}.
+   *
+   * <p>Requests for byte buffers can happen in parallel with no guarantees of thread or order.
+   */
+  default ByteBuffer acquireByteBuffer(int capacity) {
+    return ByteBuffer.allocate(capacity);
+  }
+
+  /**
+   * Release ownership of a previously acquired byte buffer.
+   *
+   * <p>After a byte buffer is released it is free to be reclaimed or reused by other requests.
+   *
+   * <p>The release of a buffer will only happen once for each acquired buffer and it will happen
+   * only on the same thread that acquired it.
+   */
+  default void releaseByteBuffer(ByteBuffer buffer) {
+    // Implicitly reclaimed by GC.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ByteDataView.java b/src/main/java/com/android/tools/r8/ByteDataView.java
new file mode 100644
index 0000000..65afbb1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ByteDataView.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.util.Arrays;
+
+/** Byte data view of a buffer that is possibly larger than the data content. */
+@Keep
+public final class ByteDataView {
+  private byte[] buffer;
+  private final int offset;
+  private final int length;
+
+  /** Create a view spanning an entire byte array. */
+  public static ByteDataView of(byte[] data) {
+    return new ByteDataView(data, 0, data.length);
+  }
+
+  /**
+   * Create a view of a byte array.
+   *
+   * <p>The view starts at {@param offset} and runs {@param length} bytes.
+   */
+  public ByteDataView(byte[] buffer, int offset, int length) {
+    assert offset >= 0;
+    assert length >= 0;
+    assert offset + length <= buffer.length;
+    this.buffer = buffer;
+    this.offset = offset;
+    this.length = length;
+  }
+
+  /** Get the full backing buffer of the byte data view. */
+  public byte[] getBuffer() {
+    assert buffer != null;
+    return buffer;
+  }
+
+  /** Get the offset at which the view starts. */
+  public int getOffset() {
+    assert buffer != null;
+    return offset;
+  }
+
+  /**
+   * Get the length of the data content.
+   *
+   * <p>Note that this does not include the offset.
+   */
+  public int getLength() {
+    assert buffer != null;
+    return length;
+  }
+
+  /** Get a copy of the data truncated to the actual data view. */
+  public byte[] copyByteData() {
+    return Arrays.copyOfRange(buffer, offset, offset + length);
+  }
+
+  public void invalidate() {
+    buffer = null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 3bab0ab..3521429 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -38,11 +38,14 @@
    * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
    * then the compiler guaranties to exit with an error.
    *
+   * <p>The {@link ByteDataView} {@param data} object can only be assumed valid during the duration
+   * of the accept. If the bytes are needed beyond that, a copy must be made elsewhere.
+   *
    * @param data Java class-file encoded data.
    * @param descriptor Class descriptor of the class the data pertains to.
    * @param handler Diagnostics handler for reporting.
    */
-  void accept(byte[] data, String descriptor, DiagnosticsHandler handler);
+  void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler);
 
   /** Empty consumer to request the production of the resource but ignore its value. */
   static ClassFileConsumer emptyConsumer() {
@@ -67,7 +70,7 @@
     }
 
     @Override
-    public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
       if (consumer != null) {
         consumer.accept(data, descriptor, handler);
       }
@@ -116,7 +119,7 @@
     }
 
     @Override
-    public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
       super.accept(data, descriptor, handler);
       outputBuilder.addFile(getClassFileName(descriptor), data, handler);
     }
@@ -196,7 +199,7 @@
     }
 
     @Override
-    public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
       super.accept(data, descriptor, handler);
       outputBuilder.addFile(ArchiveConsumer.getClassFileName(descriptor), data, handler);
     }
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
index 311a3dd..ecc2e1c 100644
--- a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.utils.DirectoryBuilder;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.OutputBuilder;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
@@ -30,7 +31,7 @@
  * <p>This consumer receives DEX file content for each Java class-file input.
  */
 @KeepForSubclassing
-public interface DexFilePerClassFileConsumer extends ProgramConsumer {
+public interface DexFilePerClassFileConsumer extends ProgramConsumer, ByteBufferProvider {
 
   /**
    * Callback to receive DEX data for a single Java class-file input and its companion classes.
@@ -41,16 +42,35 @@
    * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
    * then the compiler guaranties to exit with an error.
    *
+   * <p>The {@link ByteDataView} {@param data} object can only be assumed valid during the duration
+   * of the accept. If the bytes are needed beyond that, a copy must be made elsewhere.
+   *
    * @param primaryClassDescriptor Class descriptor of the class from the input class-file.
-   * @param data DEX encoded data.
+   * @param data DEX encoded data in a ByteDataView wrapper.
    * @param descriptors Class descriptors for all classes defined in the DEX data.
    * @param handler Diagnostics handler for reporting.
    */
-  void accept(
+  default void accept(
+      String primaryClassDescriptor,
+      ByteDataView data,
+      Set<String> descriptors,
+      DiagnosticsHandler handler) {
+    // To avoid breaking binary compatiblity, old consumers not implementing the new API will be
+    // forwarded to. New consumers must implement the accept on ByteDataView.
+    accept(primaryClassDescriptor, data.copyByteData(), descriptors, handler);
+  }
+
+  // Any new implementation should not use or call the deprecated accept method.
+  @Deprecated
+  default void accept(
       String primaryClassDescriptor,
       byte[] data,
       Set<String> descriptors,
-      DiagnosticsHandler handler);
+      DiagnosticsHandler handler) {
+    handler.error(
+        new StringDiagnostic(
+            "Deprecated use of DexFilePerClassFileConsumer::accept(..., byte[], ...)"));
+  }
 
   /** Empty consumer to request the production of the resource but ignore its value. */
   static DexFilePerClassFileConsumer emptyConsumer() {
@@ -77,7 +97,7 @@
     @Override
     public void accept(
         String primaryClassDescriptor,
-        byte[] data,
+        ByteDataView data,
         Set<String> descriptors,
         DiagnosticsHandler handler) {
       if (consumer != null) {
@@ -135,7 +155,7 @@
     @Override
     public void accept(
         String primaryClassDescriptor,
-        byte[] data,
+        ByteDataView data,
         Set<String> descriptors,
         DiagnosticsHandler handler) {
       super.accept(primaryClassDescriptor, data, descriptors, handler);
@@ -217,14 +237,13 @@
     @Override
     public void accept(
         String primaryClassDescriptor,
-        byte[] data,
+        ByteDataView data,
         Set<String> descriptors,
         DiagnosticsHandler handler) {
       super.accept(primaryClassDescriptor, data, descriptors, handler);
       outputBuilder.addFile(getDexFileName(primaryClassDescriptor), data, handler);
     }
 
-
     @Override
     public void accept(DataDirectoryResource directory, DiagnosticsHandler handler) {
       outputBuilder.addDirectory(directory.getName(), handler);
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index e802103..118df2e 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.OutputBuilder;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
@@ -32,7 +33,7 @@
  * than a single DEX file. This is the default consumer for DEX programs.
  */
 @KeepForSubclassing
-public interface DexIndexedConsumer extends ProgramConsumer {
+public interface DexIndexedConsumer extends ProgramConsumer, ByteBufferProvider {
 
   /**
    * Callback to receive DEX data for a compilation output.
@@ -46,12 +47,28 @@
    * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
    * then the compiler guaranties to exit with an error.
    *
+   * <p>The {@link ByteDataView} {@param data} object can only be assumed valid during the duration
+   * of the accept. If the bytes are needed beyond that, a copy must be made elsewhere.
+   *
    * @param fileIndex Index of the DEX file for multi-dexing. Files are zero-indexed.
-   * @param data DEX encoded data.
+   * @param data DEX encoded data in a ByteDataView wrapper.
    * @param descriptors Class descriptors for all classes defined in the DEX data.
    * @param handler Diagnostics handler for reporting.
    */
-  void accept(int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler);
+  default void accept(
+      int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+    // To avoid breaking binary compatiblity, old consumers not implementing the new API will be
+    // forwarded to. New consumers must implement the accept on ByteDataView.
+    accept(fileIndex, data.copyByteData(), descriptors, handler);
+  }
+
+  // Any new implementation should not use or call the deprecated accept method.
+  @Deprecated
+  default void accept(
+      int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+    handler.error(
+        new StringDiagnostic("Deprecated use of DexIndexedConsumer::accept(..., byte[], ...)"));
+  }
 
   /** Empty consumer to request the production of the resource but ignore its value. */
   static DexIndexedConsumer emptyConsumer() {
@@ -87,7 +104,7 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       if (consumer != null) {
         consumer.accept(fileIndex, data, descriptors, handler);
       }
@@ -141,7 +158,7 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       super.accept(fileIndex, data, descriptors, handler);
       outputBuilder.addFile(getDexFileName(fileIndex), data, handler);
     }
@@ -219,7 +236,7 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       super.accept(fileIndex, data, descriptors, handler);
       try {
         prepareDirectory();
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 734dc4a..d939dbe 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -72,12 +72,14 @@
       throws IOException, ResourceException {
     if (FileUtils.isVDexFile(file)) {
       PathOrigin vdexOrigin = new PathOrigin(file);
-      VDexReader vdexReader = new VDexReader(vdexOrigin, Files.newInputStream(file));
-      VDexParser vDexParser = new VDexParser(vdexReader);
-      int index = 0;
-      for (byte[] bytes : vDexParser.getDexFiles()) {
-        appBuilder.addDexProgramData(bytes, new VdexOrigin(vdexOrigin, index));
-        index++;
+      try (InputStream fileInputStream = Files.newInputStream(file)) {
+        VDexReader vdexReader = new VDexReader(vdexOrigin, fileInputStream);
+        VDexParser vDexParser = new VDexParser(vdexReader);
+        int index = 0;
+        for (byte[] bytes : vDexParser.getDexFiles()) {
+          appBuilder.addDexProgramData(bytes, new VdexOrigin(vdexOrigin, index));
+          index++;
+        }
       }
     } else {
       appBuilder.addProgramFiles(file);
diff --git a/src/main/java/com/android/tools/r8/JarDiff.java b/src/main/java/com/android/tools/r8/JarDiff.java
index dad57d4..025f637 100644
--- a/src/main/java/com/android/tools/r8/JarDiff.java
+++ b/src/main/java/com/android/tools/r8/JarDiff.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.google.common.io.ByteStreams.toByteArray;
-
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexClass;
@@ -14,6 +12,7 @@
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StreamUtils;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -135,7 +134,8 @@
 
   private byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
       throws Exception {
-    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+    return StreamUtils.StreamToByteArrayClose(
+        inputJar.getProgramResource(descriptor).getByteStream());
   }
 
   private DexProgramClass getDexProgramClass(Path path, byte[] bytes) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6f9464c..212264d 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.3.14-dev";
+  public static final String LABEL = "1.3.15-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
index 807509a..20571ba 100644
--- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.benchmarks.BenchmarkUtils.printRuntimeNanoseconds;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
@@ -117,11 +118,11 @@
           @Override
           public synchronized void accept(
               String primaryClassDescriptor,
-              byte[] data,
+              ByteDataView data,
               Set<String> descriptors,
               DiagnosticsHandler handler) {
-            ProgramResource resource =
-                ProgramResource.fromBytes(Origin.unknown(), Kind.DEX, data, descriptors);
+            ProgramResource resource = ProgramResource.fromBytes(
+                Origin.unknown(), Kind.DEX, data.copyByteData(), descriptors);
             for (String descriptor : descriptors) {
               assert !outputs.containsKey(descriptor);
               if (provider.resources.containsKey(descriptor)) {
@@ -159,11 +160,11 @@
           @Override
           public synchronized void accept(
               String primaryClassDescriptor,
-              byte[] data,
+              ByteDataView data,
               Set<String> descriptors,
               DiagnosticsHandler handler) {
-            ProgramResource resource =
-                ProgramResource.fromBytes(Origin.unknown(), Kind.DEX, data, descriptors);
+            ProgramResource resource = ProgramResource.fromBytes(
+                Origin.unknown(), Kind.DEX, data.copyByteData(), descriptors);
             for (String descriptor : descriptors) {
               if (provider.resources.containsKey(descriptor)) {
                 outputs.put(descriptor, resource);
diff --git a/src/main/java/com/android/tools/r8/benchmarks/IncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/IncrementalDexingBenchmark.java
index 3f241a0..477fd63 100644
--- a/src/main/java/com/android/tools/r8/benchmarks/IncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/IncrementalDexingBenchmark.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8;
@@ -30,7 +31,7 @@
                   @Override
                   public void accept(
                       int fileIndex,
-                      byte[] data,
+                      ByteDataView data,
                       Set<String> descriptors,
                       DiagnosticsHandler handler) {
                     if (fileIndex != 0) {
diff --git a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
index 2621979..f4f3c7e 100644
--- a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compatdexbuilder;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompatDxHelper;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
@@ -44,10 +45,10 @@
 
     @Override
     public synchronized void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       super.accept(fileIndex, data, descriptors, handler);
       assert bytes == null;
-      bytes = data;
+      bytes = data.copyByteData();
     }
 
     byte[] getBytes() {
@@ -134,7 +135,7 @@
           ZipEntry entry = entries.nextElement();
           if (!entry.getName().endsWith(".class")) {
             try (InputStream stream = zipFile.getInputStream(entry)) {
-              addEntry(entry.getName(), stream, entry.getTime(), out);
+              addEntry(entry.getName(), stream, out);
             }
           } else {
             toDex.add(entry);
@@ -149,7 +150,7 @@
         for (int i = 0; i < futures.size(); i++) {
           ZipEntry entry = toDex.get(i);
           DexConsumer consumer = futures.get(i).get();
-          addEntry(entry.getName() + ".dex", consumer.getBytes(), entry.getTime(), out);
+          addEntry(entry.getName() + ".dex", consumer.getBytes(), out);
         }
       }
     } finally {
@@ -177,20 +178,19 @@
     return consumer;
   }
 
-  private static void addEntry(String name, InputStream stream, long time, ZipOutputStream out)
+  private static void addEntry(String name, InputStream stream, ZipOutputStream out)
       throws IOException {
-    addEntry(name, ByteStreams.toByteArray(stream), time, out);
+    addEntry(name, ByteStreams.toByteArray(stream), out);
   }
 
-  private static void addEntry(String name, byte[] bytes, long time, ZipOutputStream out)
-      throws IOException {
+  private static void addEntry(String name, byte[] bytes, ZipOutputStream out) throws IOException {
     ZipEntry zipEntry = new ZipEntry(name);
     CRC32 crc32 = new CRC32();
     crc32.update(bytes);
     zipEntry.setSize(bytes.length);
     zipEntry.setMethod(ZipEntry.STORED);
     zipEntry.setCrc(crc32.getValue());
-    zipEntry.setTime(time);
+    zipEntry.setTime(0);
     out.putNextEntry(zipEntry);
     out.write(bytes);
     out.closeEntry();
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 7d2907a..7321b53 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compatdx;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompatDxHelper;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
@@ -24,9 +25,11 @@
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -501,20 +504,21 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       if (fileIndex > 0) {
         throw new CompilationError(
             "Compilation result could not fit into a single dex file. "
                 + "Reduce the input-program size or run with --multi-dex enabled");
       }
       assert bytes == null;
-      bytes = data;
+      // Store a copy of the bytes as we may not assume the backing is valid after accept returns.
+      bytes = data.copyByteData();
     }
 
     @Override
     public void finished(DiagnosticsHandler handler) {
       if (bytes != null) {
-        super.accept(0, bytes, null, handler);
+        super.accept(0, ByteDataView.of(bytes), null, handler);
       }
       super.finished(handler);
     }
@@ -530,14 +534,12 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
-      try {
-        Files.write(
-            output,
-            data,
-            StandardOpenOption.CREATE,
-            StandardOpenOption.TRUNCATE_EXISTING,
-            StandardOpenOption.WRITE);
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+      StandardOpenOption[] options = {
+        StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING
+      };
+      try (OutputStream stream = new BufferedOutputStream(Files.newOutputStream(output, options))) {
+        stream.write(data.getBuffer(), data.getOffset(), data.getLength());
       } catch (IOException e) {
         handler.error(new ExceptionDiagnostic(e, new PathOrigin(output)));
       }
@@ -573,8 +575,8 @@
               ZipEntry entry = entries.nextElement();
               if (ZipUtils.isClassFile(entry.getName())) {
                 try (InputStream entryStream = zipFile.getInputStream(entry)) {
-                  outputBuilder.addFile(
-                      entry.getName(), ByteStreams.toByteArray(entryStream), handler);
+                  byte[] bytes = ByteStreams.toByteArray(entryStream);
+                  outputBuilder.addFile(entry.getName(), ByteDataView.of(bytes), handler);
                 }
               }
             }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index f9e95ec..a2ef57d 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -3,14 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.ByteBufferProvider;
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -228,30 +233,40 @@
           dexDataFutures.add(
               executorService.submit(
                   () -> {
-                    byte[] result = writeDexFile(mapping);
+                    ProgramConsumer consumer;
+                    ByteBufferProvider byteBufferProvider;
                     if (programConsumer != null) {
-                      programConsumer.accept(
-                          virtualFile.getId(),
-                          result,
-                          virtualFile.getClassDescriptors(),
-                          options.reporter);
+                      consumer = programConsumer;
+                      byteBufferProvider = programConsumer;
                     } else if (virtualFile.getPrimaryClassDescriptor() != null) {
-                      options
-                          .getDexFilePerClassFileConsumer()
+                      consumer = options.getDexFilePerClassFileConsumer();
+                      byteBufferProvider = options.getDexFilePerClassFileConsumer();
+                    } else {
+                      consumer = options.getDexIndexedConsumer();
+                      byteBufferProvider = options.getDexIndexedConsumer();
+                    }
+                    ByteBufferResult result = writeDexFile(mapping, byteBufferProvider);
+                    ByteDataView data =
+                        new ByteDataView(
+                            result.buffer.array(), result.buffer.arrayOffset(), result.length);
+                    if (consumer instanceof DexFilePerClassFileConsumer) {
+                      ((DexFilePerClassFileConsumer) consumer)
                           .accept(
                               virtualFile.getPrimaryClassDescriptor(),
-                              result,
+                              data,
                               virtualFile.getClassDescriptors(),
                               options.reporter);
                     } else {
-                      options
-                          .getDexIndexedConsumer()
+                      ((DexIndexedConsumer) consumer)
                           .accept(
                               virtualFile.getId(),
-                              result,
+                              data,
                               virtualFile.getClassDescriptors(),
                               options.reporter);
                     }
+                    // Release use of the backing buffer now that accept has returned.
+                    data.invalidate();
+                    byteBufferProvider.releaseByteBuffer(result.buffer);
                     return true;
                   }));
         }
@@ -451,8 +466,9 @@
     }
   }
 
-  private byte[] writeDexFile(ObjectToOffsetMapping mapping) {
-    FileWriter fileWriter = new FileWriter(mapping, application, options, namingLens);
+  private ByteBufferResult writeDexFile(
+      ObjectToOffsetMapping mapping, ByteBufferProvider provider) {
+    FileWriter fileWriter = new FileWriter(provider, mapping, application, options, namingLens);
     // Collect the non-fixed sections.
     fileWriter.collect();
     // Generate and write the bytes.
diff --git a/src/main/java/com/android/tools/r8/dex/BinaryReader.java b/src/main/java/com/android/tools/r8/dex/BinaryReader.java
index 127e1d5..5d51238 100644
--- a/src/main/java/com/android/tools/r8/dex/BinaryReader.java
+++ b/src/main/java/com/android/tools/r8/dex/BinaryReader.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.LebUtils;
-import com.google.common.io.ByteStreams;
+import com.android.tools.r8.utils.StreamUtils;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
@@ -20,7 +20,7 @@
   protected final ByteBuffer buffer;
 
   protected BinaryReader(ProgramResource resource) throws ResourceException, IOException {
-    this(resource.getOrigin(), ByteStreams.toByteArray(resource.getByteStream()));
+    this(resource.getOrigin(), StreamUtils.StreamToByteArrayClose(resource.getByteStream()));
   }
 
   protected BinaryReader(Origin origin, byte[] bytes) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
index fac5e53..000bc0e 100644
--- a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
@@ -3,10 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.utils.EncodedValueUtils;
 import com.android.tools.r8.utils.LebUtils;
+import com.google.common.annotations.VisibleForTesting;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.ShortBuffer;
@@ -18,28 +21,59 @@
 public class DexOutputBuffer {
   private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
 
+  private final ByteBufferProvider byteBufferProvider;
   private ByteBuffer byteBuffer;
 
-  public DexOutputBuffer() {
-    byteBuffer = allocate(DEFAULT_BUFFER_SIZE);
+  @VisibleForTesting
+  DexOutputBuffer() {
+    this(new ByteBufferProvider() {});
+  }
+
+  public DexOutputBuffer(ByteBufferProvider byteBufferProvider) {
+    this.byteBufferProvider = byteBufferProvider;
+    byteBuffer = allocateByteBuffer(DEFAULT_BUFFER_SIZE);
   }
 
   private void ensureSpaceFor(int bytes) {
     if (byteBuffer.remaining() < bytes) {
       int newSize = byteBuffer.capacity() + Math.max(byteBuffer.capacity(), bytes * 2);
-      ByteBuffer newBuffer = allocate(newSize);
+      ByteBuffer newBuffer = allocateByteBuffer(newSize);
       System.arraycopy(byteBuffer.array(), 0, newBuffer.array(), 0, byteBuffer.position());
       newBuffer.position(byteBuffer.position());
+      freeByteBuffer(byteBuffer);
       byteBuffer = newBuffer;
     }
   }
 
-  private ByteBuffer allocate(int size) {
-    ByteBuffer buffer = ByteBuffer.allocate(size);
+  private ByteBuffer allocateByteBuffer(int size) {
+    ByteBuffer buffer = byteBufferProvider.acquireByteBuffer(size);
+    if (!buffer.hasArray()) {
+      throw new CompilationError(
+          "Provided byte-buffer is required to have an array backing, but does not.");
+    }
+    if (buffer.capacity() < size) {
+      throw new CompilationError(
+          "Insufficient capacity of provided byte-buffer."
+              + " Requested capacity "
+              + size
+              + ", actual capacity: "
+              + buffer.capacity());
+    }
+    if (buffer.position() != 0) {
+      throw new CompilationError(
+          "Provided byte-buffer is required to start at position zero, but starts at "
+              + buffer.position()
+              + ".");
+    }
     buffer.order(ByteOrder.LITTLE_ENDIAN);
     return buffer;
   }
 
+  private void freeByteBuffer(ByteBuffer buffer) {
+    assert buffer != null;
+    byteBufferProvider.releaseByteBuffer(buffer);
+  }
+
   public void putUleb128(int value) {
     LebUtils.putUleb128(this, value);
   }
@@ -130,4 +164,10 @@
   public byte[] asArray() {
     return byteBuffer.array();
   }
+
+  public ByteBuffer stealByteBuffer() {
+    ByteBuffer buffer = byteBuffer;
+    byteBuffer = null;
+    return buffer;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index b73f8a4..f1b14b3 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
 
 import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.Descriptor;
@@ -56,6 +57,7 @@
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import java.nio.ByteBuffer;
 import java.security.MessageDigest;
 import java.util.Arrays;
 import java.util.Collection;
@@ -72,14 +74,27 @@
 
 public class FileWriter {
 
+  /** Simple pair of a byte buffer and its written length. */
+  public static class ByteBufferResult {
+    // Ownership of the buffer is transferred to the receiver of this result structure.
+    public final ByteBuffer buffer;
+    public final int length;
+
+    private ByteBufferResult(ByteBuffer buffer, int length) {
+      this.buffer = buffer;
+      this.length = length;
+    }
+  }
+
   private final ObjectToOffsetMapping mapping;
   private final DexApplication application;
   private final InternalOptions options;
   private final NamingLens namingLens;
-  private final DexOutputBuffer dest = new DexOutputBuffer();
+  private final DexOutputBuffer dest;
   private final MixedSectionOffsets mixedSectionOffsets;
 
   public FileWriter(
+      ByteBufferProvider provider,
       ObjectToOffsetMapping mapping,
       DexApplication application,
       InternalOptions options,
@@ -88,6 +103,7 @@
     this.application = application;
     this.options = options;
     this.namingLens = namingLens;
+    this.dest = new DexOutputBuffer(provider);
     this.mixedSectionOffsets = new MixedSectionOffsets(options);
   }
 
@@ -134,7 +150,7 @@
     return this;
   }
 
-  public byte[] generate() {
+  public ByteBufferResult generate() {
     // Check restrictions on interface methods.
     checkInterfaceMethods();
 
@@ -198,8 +214,8 @@
     writeSignature(layout);
     writeChecksum(layout);
 
-    // Turn into an array
-    return Arrays.copyOf(dest.asArray(), layout.getEndOfFile());
+    // Wrap backing buffer with actual length.
+    return new ByteBufferResult(dest.stealByteBuffer(), layout.getEndOfFile());
   }
 
   private void checkInterfaceMethods() {
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index ba576e7..b5db1ae 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.dexfilemerger;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexFileMergerHelper;
@@ -210,12 +211,14 @@
 
     @Override
     public synchronized void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       if (singleFixedFileIndex != null && fileIndex != 0) {
         handler.error(new StringDiagnostic("Result does not fit into a single dex file."));
         return;
       }
-      writers.put(fileIndex, () -> writeEntry(fileIndex, data, descriptors, handler));
+      // Make a copy of the actual bytes as they will possibly be accessed later by the runner.
+      final byte[] bytes = data.copyByteData();
+      writers.put(fileIndex, () -> writeEntry(fileIndex, bytes, descriptors, handler));
 
       while (writers.containsKey(highestIndexWritten + 1)) {
         ++highestIndexWritten;
@@ -243,7 +246,10 @@
         int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
       try {
         ZipUtils.writeToZipStream(
-            getStream(handler), getDexFileName(fileIndex), data, ZipEntry.DEFLATED, true);
+            getStream(handler),
+            getDexFileName(fileIndex),
+            ByteDataView.of(data),
+            ZipEntry.DEFLATED);
         hasWrittenSomething = true;
       } catch (IOException e) {
         handler.error(new ExceptionDiagnostic(e, origin));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index f92eb80..a38e22b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -70,8 +70,8 @@
     Instruction replacement = null;
     ValueType valueType = instruction.outValue().outType();
     if (rule != null && rule.hasReturnValue() && rule.getReturnValue().isSingleValue()) {
-      assert valueType != ValueType.OBJECT;
       Value value = code.createValue(valueType, instruction.getLocalInfo());
+      assert valueType != ValueType.OBJECT || rule.getReturnValue().isNull();
       replacement = new ConstNumber(value, rule.getReturnValue().getSingleValue());
     }
     if (replacement == null &&
@@ -132,12 +132,12 @@
         DexEncodedMethod definition = appInfo
             .lookup(invoke.getType(), invokedMethod, callingContext);
 
-        // Process invokes marked as having no side effects.
         boolean invokeReplaced = false;
         ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
         if (lookup != null) {
           if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
               && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
+            // Remove invoke if marked as having no side effects and the return value is not used.
             iterator.remove();
             invokeReplaced = true;
           } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
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 6537667..9562ac1 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -5,6 +5,7 @@
 
 import static org.objectweb.asm.Opcodes.ASM6;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.errors.Unimplemented;
@@ -168,7 +169,7 @@
       verifyCf(result);
     }
     ExceptionUtils.withConsumeResourceHandler(
-        options.reporter, handler -> consumer.accept(result, desc, handler));
+        options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
   }
 
   private int getClassFileVersion(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index d1a0b3d..740aa2c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -968,6 +968,8 @@
                     ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
                   } else if (acceptString("false")) {
                     ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
+                  } else if (acceptString("null")) {
+                    ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue());
                   } else {
                     TextPosition fieldOrValueStart = getPosition();
                     String qualifiedFieldNameOrInteger = acceptFieldNameOrIntegerForReturn();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
index b4e9596..a1aad20 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
@@ -8,39 +8,60 @@
 import com.android.tools.r8.utils.LongInterval;
 
 public class ProguardMemberRuleReturnValue {
+  public enum Type {
+    BOOLEAN,
+    VALUE_RANGE,
+    FIELD,
+    NULL
+  }
 
+  private final Type type;
   private final boolean booleanValue;
   private final LongInterval longInterval;
   private final DexField field;
 
   ProguardMemberRuleReturnValue(boolean value) {
+    this.type = Type.BOOLEAN;
     this.booleanValue = value;
     this.longInterval = null;
     this.field = null;
   }
 
   ProguardMemberRuleReturnValue(LongInterval value) {
+    this.type = Type.VALUE_RANGE;
     this.booleanValue = false;
     this.longInterval = value;
     this.field = null;
   }
 
   ProguardMemberRuleReturnValue(DexField field) {
+    this.type = Type.FIELD;
     this.booleanValue = false;
     this.longInterval = null;
     this.field = field;
   }
 
+  ProguardMemberRuleReturnValue() {
+    this.type = Type.NULL;
+    this.booleanValue = false;
+    this.longInterval = null;
+    this.field = null;
+  }
+
   public boolean isBoolean() {
-    return longInterval == null && field == null;
+    return type == Type.BOOLEAN;
   }
 
   public boolean isValueRange() {
-    return longInterval != null && field == null;
+    return type == Type.VALUE_RANGE;
   }
 
   public boolean isField() {
-    return field != null;
+    return type == Type.FIELD;
+  }
+
+  public boolean isNull() {
+    return type == Type.NULL;
   }
 
   public boolean getBoolean() {
@@ -51,22 +72,27 @@
   /**
    * Returns if this return value is a single value.
    *
-   * Boolean values are considered a single value.
+   * Boolean values and null are considered a single value.
    */
   public boolean isSingleValue() {
-    return isBoolean() || (isValueRange() && longInterval.isSingleValue());
+    return isBoolean() || isNull() || (isValueRange() && longInterval.isSingleValue());
   }
 
   /**
    * Returns the return value.
    *
    * Boolean values are returned as 0 for <code>false</code> and 1 for <code>true</code>.
+   *
+   * Reference value <code>null</code> is returned as 0.
    */
   public long getSingleValue() {
     assert isSingleValue();
     if (isBoolean()) {
       return booleanValue ? 1 : 0;
     }
+    if (isNull()) {
+      return 0;
+    }
     return longInterval.getSingleValue();
   }
 
@@ -86,6 +112,8 @@
     result.append(" return ");
     if (isBoolean()) {
       result.append(booleanValue ? "true" : "false");
+    } else if (isNull()) {
+      result.append("null");
     } else if (isValueRange()) {
       result.append(longInterval.getMin());
       if (!isSingleValue()) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
index 14bf6f5..4371edd 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.BaseCompilerCommand;
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
@@ -84,9 +85,12 @@
 
           @Override
           public void accept(
-              int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+              int fileIndex,
+              ByteDataView data,
+              Set<String> descriptors,
+              DiagnosticsHandler handler) {
             super.accept(fileIndex, data, descriptors, handler);
-            addDexFile(fileIndex, data, descriptors);
+            addDexFile(fileIndex, data.copyByteData(), descriptors);
           }
 
           @Override
@@ -121,11 +125,11 @@
           @Override
           public void accept(
               String primaryClassDescriptor,
-              byte[] data,
+              ByteDataView data,
               Set<String> descriptors,
               DiagnosticsHandler handler) {
             super.accept(primaryClassDescriptor, data, descriptors, handler);
-            addDexFile(primaryClassDescriptor, data, descriptors);
+            addDexFile(primaryClassDescriptor, data.copyByteData(), descriptors);
           }
 
           synchronized void addDexFile(
@@ -157,9 +161,9 @@
           private List<DescriptorsWithContents> files = new ArrayList<>();
 
           @Override
-          public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+          public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
             super.accept(data, descriptor, handler);
-            addClassFile(data, descriptor);
+            addClassFile(data.copyByteData(), descriptor);
           }
 
           synchronized void addClassFile(byte[] data, String descriptor) {
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 1a64ab0..4111bed 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResource;
 import com.android.tools.r8.DiagnosticsHandler;
@@ -89,6 +90,7 @@
       name += DataResource.SEPARATOR;
     }
     ZipEntry entry = new ZipEntry(name);
+    entry.setTime(0);
     ZipOutputStream zip = getStream(handler);
     synchronized (this) {
       try {
@@ -103,7 +105,7 @@
   @Override
   public void addFile(String name, DataEntryResource content, DiagnosticsHandler handler) {
     try (InputStream in = content.getByteStream()) {
-      addFile(name, ByteStreams.toByteArray(in), handler);
+      addFile(name, ByteDataView.of(ByteStreams.toByteArray(in)), handler);
     } catch (IOException e) {
       handleIOException(e, handler);
     } catch (ResourceException e) {
@@ -112,8 +114,8 @@
     }
   }
 
-   @Override
-   public synchronized void addFile(String name, byte[] content, DiagnosticsHandler handler) {
+  @Override
+  public synchronized void addFile(String name, ByteDataView content, DiagnosticsHandler handler) {
     try {
       ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
     } catch (IOException e) {
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
index 2058d39..fc5ee55 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.ResourceException;
@@ -46,7 +47,7 @@
   @Override
   public void addFile(String name, DataEntryResource content, DiagnosticsHandler handler) {
     try (InputStream in = content.getByteStream()) {
-      addFile(name, ByteStreams.toByteArray(in), handler);
+      addFile(name, ByteDataView.of(ByteStreams.toByteArray(in)), handler);
     } catch (IOException e) {
       handler.error(new ExceptionDiagnostic(e, content.getOrigin()));
     } catch (ResourceException e) {
@@ -56,7 +57,7 @@
   }
 
   @Override
-  public synchronized void addFile(String name, byte[] content, DiagnosticsHandler handler) {
+  public synchronized void addFile(String name, ByteDataView content, DiagnosticsHandler handler) {
     Path target = root.resolve(name.replace(NAME_SEPARATOR, File.separatorChar));
     try {
       Files.createDirectories(target.getParent());
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index feb57a5..b64f9ac 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ByteDataView;
 import com.google.common.io.Closer;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -148,6 +149,11 @@
 
   public static void writeToFile(Path output, OutputStream defValue, byte[] contents)
       throws IOException {
+    writeToFile(output, defValue, ByteDataView.of(contents));
+  }
+
+  public static void writeToFile(Path output, OutputStream defValue, ByteDataView contents)
+      throws IOException {
     try (Closer closer = Closer.create()) {
       OutputStream outputStream =
           openPathWithDefault(
@@ -157,7 +163,7 @@
               StandardOpenOption.CREATE,
               StandardOpenOption.TRUNCATE_EXISTING,
               StandardOpenOption.WRITE);
-      outputStream.write(contents);
+      outputStream.write(contents.getBuffer(), contents.getOffset(), contents.getLength());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
index 9237485..9077f7b 100644
--- a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.origin.Origin;
@@ -20,7 +21,7 @@
 
   void addFile(String name, DataEntryResource content, DiagnosticsHandler handler);
 
-  void addFile(String name, byte[] content, DiagnosticsHandler handler);
+  void addFile(String name, ByteDataView content, DiagnosticsHandler handler);
 
   Path getPath();
 
diff --git a/src/main/java/com/android/tools/r8/utils/StreamUtils.java b/src/main/java/com/android/tools/r8/utils/StreamUtils.java
new file mode 100644
index 0000000..8d4ebc3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/StreamUtils.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class StreamUtils {
+  /**
+   * Read all data from the stream into a byte[], close the stream and return the bytes.
+   * @return The bytes of the stream
+   */
+  public static byte[] StreamToByteArrayClose(InputStream inputStream) throws IOException {
+    byte[] result = ByteStreams.toByteArray(inputStream);
+    inputStream.close();
+    return result;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index fd50583..97f3699 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.MODULE_INFO_CLASS;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.io.ByteStreams;
 import java.io.File;
@@ -73,27 +74,24 @@
   public static void writeToZipStream(
       ZipOutputStream stream, String entry, byte[] content, int compressionMethod)
       throws IOException {
-    writeToZipStream(stream, entry, content, compressionMethod, false);
+    writeToZipStream(stream, entry, ByteDataView.of(content), compressionMethod);
   }
 
   public static void writeToZipStream(
-      ZipOutputStream stream,
-      String entry,
-      byte[] content,
-      int compressionMethod,
-      boolean setZeroTime)
+      ZipOutputStream stream, String entry, ByteDataView content, int compressionMethod)
       throws IOException {
+    byte[] buffer = content.getBuffer();
+    int offset = content.getOffset();
+    int length = content.getLength();
     CRC32 crc = new CRC32();
-    crc.update(content);
+    crc.update(buffer, offset, length);
     ZipEntry zipEntry = new ZipEntry(entry);
     zipEntry.setMethod(compressionMethod);
-    zipEntry.setSize(content.length);
+    zipEntry.setSize(length);
     zipEntry.setCrc(crc.getValue());
-    if (setZeroTime) {
-      zipEntry.setTime(0);
-    }
+    zipEntry.setTime(0);
     stream.putNextEntry(zipEntry);
-    stream.write(content);
+    stream.write(buffer, offset, length);
     stream.closeEntry();
   }
 
diff --git a/src/test/examples/assumenosideeffects6/Assumenosideeffects.java b/src/test/examples/assumenosideeffects6/Assumenosideeffects.java
new file mode 100644
index 0000000..07630b9
--- /dev/null
+++ b/src/test/examples/assumenosideeffects6/Assumenosideeffects.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package assumenosideeffects6;
+
+class A {
+  @CheckDiscarded
+  public static Object methodStaticNull() {
+    System.out.println("methodStaticNull");
+    return new Object();
+  }
+
+  // This method must be on a class which is not explicitly kept (as Assumenosideeffects is).
+  // For an explicitly kept class we cannot expect single target for any methods, so the rule
+  // will never apply (see b/70550443#comment2).
+  @CheckDiscarded
+  public Object methodNull() {
+    System.out.println("methodNull");
+    return new Object();
+  }
+}
+
+public class Assumenosideeffects {
+  public static void main(String[] args) {
+    System.out.println(A.methodStaticNull() != null ? "NOT NULL" : "NULL");
+    System.out.println((new A().methodNull()) != null ? "NOT NULL" : "NULL");
+  }
+}
diff --git a/src/test/examples/assumenosideeffects6/CheckDiscarded.java b/src/test/examples/assumenosideeffects6/CheckDiscarded.java
new file mode 100644
index 0000000..a249a6d
--- /dev/null
+++ b/src/test/examples/assumenosideeffects6/CheckDiscarded.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package assumenosideeffects6;
+
+public @interface CheckDiscarded {
+
+}
diff --git a/src/test/examples/assumenosideeffects6/keep-rules-discard.txt b/src/test/examples/assumenosideeffects6/keep-rules-discard.txt
new file mode 100644
index 0000000..0f51575
--- /dev/null
+++ b/src/test/examples/assumenosideeffects6/keep-rules-discard.txt
@@ -0,0 +1,23 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class assumenosideeffects6.Assumenosideeffects {
+  public static void main(...);
+}
+
+# Mark some methods to return null and have no side effects.
+-assumenosideeffects class assumenosideeffects6.A {
+  public static java.lang.Object methodStaticNull() return null;
+  public java.lang.Object methodNull() return null;
+}
+
+# Allow access modification to enable minification.
+-allowaccessmodification
+
+# Check that methods has been discarded.
+-checkdiscard class ** {
+  @assumenosideeffects6.CheckDiscarded *;
+}
diff --git a/src/test/examples/assumenosideeffects6/keep-rules.txt b/src/test/examples/assumenosideeffects6/keep-rules.txt
new file mode 100644
index 0000000..c158dd6
--- /dev/null
+++ b/src/test/examples/assumenosideeffects6/keep-rules.txt
@@ -0,0 +1,18 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class assumenosideeffects6.Assumenosideeffects {
+  public static void main(...);
+}
+
+# Mark some methods to return null and have no side effects.
+-assumenosideeffects class assumenosideeffects6.A {
+  public static java.lang.Object methodStaticNull() return null;
+  public java.lang.Object methodNull() return null;
+}
+
+# Allow access modification to enable minification.
+-allowaccessmodification
diff --git a/src/test/examples/assumevalues7/Assumevalues.java b/src/test/examples/assumevalues7/Assumevalues.java
new file mode 100644
index 0000000..e83fc2e
--- /dev/null
+++ b/src/test/examples/assumevalues7/Assumevalues.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package assumevalues7;
+
+class A {
+  public static Object getObjectStatic() {
+    return new Object();
+  }
+
+  // This method must be on a class which is not explicitly kept (as Assumevalues is).
+  // For an explicitly kept class we cannot expect single target for any methods, so the rule
+  // will never apply (see b/70550443#comment2).
+  public Object getObject() {
+    return new Object();
+  }
+}
+
+public class Assumevalues {
+  public static void main(String[] args) {
+    if (A.getObjectStatic() != null) {
+      System.out.println("NOPE_STATIC_NOT_NULL");
+    }
+    if (new A().getObject() != null) {
+      System.out.println("NOPE_NOT_NULL");
+    }
+    System.out.println("OK");
+  }
+}
diff --git a/src/test/examples/assumevalues7/keep-rules.txt b/src/test/examples/assumevalues7/keep-rules.txt
new file mode 100644
index 0000000..c8c6c2b
--- /dev/null
+++ b/src/test/examples/assumevalues7/keep-rules.txt
@@ -0,0 +1,15 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep class assumevalues7.Assumevalues {
+  void main(...);
+}
+
+# Mark some methods returning null.
+-assumevalues class assumevalues7.A {
+  public static java.lang.Object getObjectStatic() return null;
+  public java.lang.Object getObject() return null;
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 87453d0..f23eb7b 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -367,16 +367,15 @@
     class MultiTypeConsumer implements DexIndexedConsumer, DexFilePerClassFileConsumer {
 
       @Override
-      public void accept(String primaryClassDescriptor, byte[] data, Set<String> descriptors,
-          DiagnosticsHandler handler) {
-
-      }
+      public void accept(
+          String primaryClassDescriptor,
+          ByteDataView data,
+          Set<String> descriptors,
+          DiagnosticsHandler handler) {}
 
       @Override
-      public void accept(int fileIndex, byte[] data, Set<String> descriptors,
-          DiagnosticsHandler handler) {
-
-      }
+      public void accept(
+          int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {}
 
       @Override
       public void finished(DiagnosticsHandler handler) {
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 93a43f7..a063ab2 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -27,12 +27,12 @@
                   @Override
                   public void accept(
                       int fileIndex,
-                      byte[] data,
+                      ByteDataView data,
                       Set<String> descriptors,
                       DiagnosticsHandler handler) {
                     Marker marker;
                     try {
-                      marker = ExtractMarker.extractMarkerFromDexProgramData(data);
+                      marker = ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
                     } catch (Exception e) {
                       throw new RuntimeException(e);
                     }
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
index 6533e08..6310054 100644
--- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -36,7 +36,7 @@
                   @Override
                   public void accept(
                       int fileIndex,
-                      byte[] data,
+                      ByteDataView data,
                       Set<String> descriptors,
                       DiagnosticsHandler handler) {}
 
diff --git a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
index 2dbfb37..bb88036 100644
--- a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
+++ b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
@@ -70,7 +70,7 @@
     Path inputJar = temp.getRoot().toPath().resolve("input.jar");
     ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
     String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
-    inputConsumer.accept(ToolHelper.getClassAsBytes(clazz), descriptor, null);
+    inputConsumer.accept(ByteDataView.of(ToolHelper.getClassAsBytes(clazz)), descriptor, null);
     inputConsumer.finished(null);
     return inputJar;
   }
@@ -82,7 +82,8 @@
       Path path = testDirectory.resolve(className.replace('.', '/') + ".class");
       String descriptor = DescriptorUtils.javaTypeToDescriptor(className);
       try (InputStream inputStream = new FileInputStream(path.toFile())) {
-        inputConsumer.accept(ByteStreams.toByteArray(inputStream), descriptor, null);
+        inputConsumer.accept(
+            ByteDataView.of(ByteStreams.toByteArray(inputStream)), descriptor, null);
       }
     }
     inputConsumer.finished(null);
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index d78f94b..37515d2 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1488,8 +1488,8 @@
 
                         @Override
                         public synchronized void accept(
-                            byte[] data, String descriptor, DiagnosticsHandler handler) {
-                          builder.addClassProgramData(data, cfOrigin);
+                            ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                          builder.addClassProgramData(data.copyByteData(), cfOrigin);
                         }
 
                         @Override
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
index 7d88649..4713369 100644
--- a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
@@ -5,9 +5,10 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
@@ -64,7 +65,7 @@
     Path out = temp.getRoot().toPath().resolve("aaload-null.jar");
     ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
     archiveConsumer.accept(
-        AlwaysNullGetItemDump.dump(),
+        ByteDataView.of(AlwaysNullGetItemDump.dump()),
         DescriptorUtils.javaTypeToDescriptor(CLASS.getCanonicalName()),
         null);
     archiveConsumer.finished(null);
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index 450e6d0..55ec4f1 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -63,7 +63,7 @@
   }
 
   private int countCatchHandlers(AndroidApp inputApp) throws Exception {
-    CodeInspector inspector = new CodeInspector(inputApp, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(inputApp);
     DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
     Code code = dexClass.virtualMethods()[0].getCode();
     if (code.isCfCode()) {
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
index 7db1d61..632d0b9 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -69,7 +69,7 @@
 
   private static CfInvokeDynamic findFirstInMethod(AndroidApp app) throws Exception {
     String returnType = "void";
-    CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(app);
     List<String> args = Collections.singletonList(String[].class.getTypeName());
     DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
     CfCode code = method.getCode().asCfCode();
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index da32cce..97997fe 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DexIndexedConsumer;
@@ -111,7 +112,9 @@
     ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
     for (Class<?> c : inputClasses) {
       archiveConsumer.accept(
-          getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null);
+          ByteDataView.of(getClassAsBytes(c)),
+          DescriptorUtils.javaTypeToDescriptor(c.getName()),
+          null);
     }
     archiveConsumer.finished(null);
     String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
index e4f9249..84ac87c 100644
--- a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -11,18 +11,16 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfMonitor;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.JarCode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import org.junit.Test;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodNode;
 
 public class SynchronizedNoopTestRunner {
   private byte[] data;
@@ -40,21 +38,9 @@
     CodeInspector inspector = new CodeInspector(a.build());
     DexEncodedMethod method =
         inspector.clazz(CLASS).method("void", "noop", Collections.emptyList()).getMethod();
-    ArrayList<AbstractInsnNode> insns = new ArrayList<>();
-    JarCode jarCode = method.getCode().asJarCode();
-    MethodNode node = jarCode.getNode();
-    assert node != null;
-    InsnList asmInsns = node.instructions;
-    for (int i = 0; i < asmInsns.size(); i++) {
-      insns.add(asmInsns.get(i));
-    }
-    boolean hasMonitor =
-        insns
-            .stream()
-            .anyMatch(
-                insn ->
-                    insn.getOpcode() == Opcodes.MONITORENTER
-                        || insn.getOpcode() == Opcodes.MONITOREXIT);
+    CfCode cfCode = method.getCode().asCfCode();
+    List<CfInstruction> insns = cfCode.getInstructions();
+    boolean hasMonitor = insns.stream().anyMatch(insn -> insn instanceof CfMonitor);
     assertFalse(hasMonitor);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
index f433a9d..03c28cd 100644
--- a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
@@ -40,7 +41,8 @@
     Path output = temp.getRoot().toPath().resolve("output.jar");
 
     ArchiveConsumer inputConsumer = new ArchiveConsumer(input);
-    inputConsumer.accept(clazz, DescriptorUtils.javaTypeToDescriptor(CLASS.getName()), null);
+    inputConsumer.accept(
+        ByteDataView.of(clazz), DescriptorUtils.javaTypeToDescriptor(CLASS.getName()), null);
     inputConsumer.finished(null);
     ProcessResult runInput = ToolHelper.runJava(input, CLASS.getCanonicalName());
     if (runInput.exitCode != 0) {
diff --git a/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java b/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
index 63f131e..9949a68 100644
--- a/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
+++ b/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8;
@@ -57,7 +58,7 @@
   public void test() throws Exception {
     Path inputJar = temp.getRoot().toPath().resolve("input.jar");
     ArchiveConsumer consumer = new ArchiveConsumer(inputJar);
-    consumer.accept(Dump.dump(), DESCRIPTOR, null);
+    consumer.accept(ByteDataView.of(Dump.dump()), DESCRIPTOR, null);
     consumer.finished(null);
     ProcessResult runInput = ToolHelper.runJava(inputJar, CLASS_NAME);
     assertEquals(0, runInput.exitCode);
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 6322840..2a56ef5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -866,8 +866,8 @@
     MethodSubject method = clazz.method("void", "main", ImmutableList.of("java.lang.String[]"));
     assertThat(method, isPresent());
     assertThat(
-        method.getMethod().getCode().asJarCode().toString(),
-        containsString("INVOKEINTERFACE classmerging/MergeDefaultMethodIntoClassTest$A.f"));
+        method.getMethod().getCode().asCfCode().toString(),
+        containsString("invokeinterface classmerging.MergeDefaultMethodIntoClassTest$A.f()V"));
 
     runTestOnInput(main, app, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
   }
diff --git a/src/test/java/com/android/tools/r8/d8/DexVersionTests.java b/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
index 59034a5..f413f98 100644
--- a/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
+++ b/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
@@ -77,7 +78,7 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       hasOutput = true;
     }
 
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
index bcc70c6..41a9fd4 100644
--- a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
@@ -72,7 +73,7 @@
     Path out = temp.getRoot().toPath().resolve("out.jar");
     ArchiveConsumer consumer = new ArchiveConsumer(out);
     consumer.accept(
-        ArrayDimensionGreaterThanSevenTestDump.dump(),
+        ByteDataView.of(ArrayDimensionGreaterThanSevenTestDump.dump()),
         DescriptorUtils.javaTypeToDescriptor(NAME),
         null);
     consumer.finished(null);
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index a275e6b..c7a5b16 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -90,7 +91,7 @@
   private Path buildInput(byte[] clazz) {
     Path inputJar = temp.getRoot().toPath().resolve("input.jar");
     ArchiveConsumer inputJarConsumer = new ArchiveConsumer(inputJar);
-    inputJarConsumer.accept(clazz, IincDebugTestDump.DESCRIPTOR, null);
+    inputJarConsumer.accept(ByteDataView.of(clazz), IincDebugTestDump.DESCRIPTOR, null);
     inputJarConsumer.finished(null);
     return inputJar;
   }
diff --git a/src/test/java/com/android/tools/r8/debug/LocalEndTestRunner.java b/src/test/java/com/android/tools/r8/debug/LocalEndTestRunner.java
index 982332e..3f86969 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalEndTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalEndTestRunner.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -40,7 +41,7 @@
     if (inputJarCache == null) {
       inputJarCache = temp.getRoot().toPath().resolve("input.jar");
       ClassFileConsumer jarWriter = new ArchiveConsumer(inputJarCache);
-      jarWriter.accept(LocalEndTestDump.dump(), DESC, null);
+      jarWriter.accept(ByteDataView.of(LocalEndTestDump.dump()), DESC, null);
       jarWriter.finished(null);
     }
     return inputJarCache;
diff --git a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
index 813a873..cf341fd 100644
--- a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
@@ -31,7 +32,7 @@
     Path inputJar = temp.getRoot().toPath().resolve("input.jar");
     ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
     String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
-    inputConsumer.accept(ToolHelper.getClassAsBytes(clazz), descriptor, null);
+    inputConsumer.accept(ByteDataView.of(ToolHelper.getClassAsBytes(clazz)), descriptor, null);
     inputConsumer.finished(null);
     return inputJar;
   }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index 80279d5..76d2291 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -21,7 +22,7 @@
   private Path buildInput(byte[] clazz, String descriptor) {
     Path inputJar = temp.getRoot().toPath().resolve("input.jar");
     ArchiveConsumer inputJarConsumer = new ArchiveConsumer(inputJar);
-    inputJarConsumer.accept(clazz, descriptor, null);
+    inputJarConsumer.accept(ByteDataView.of(clazz), descriptor, null);
     inputJarConsumer.finished(null);
     return inputJar;
   }
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 3b12cc4..a518a36 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.code.ConstString;
@@ -34,10 +35,14 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -161,10 +166,45 @@
 
     private final List<Set<String>> descriptors = new ArrayList<>();
 
+    private final Deque<ByteBuffer> freeBuffers = new ArrayDeque<>();
+    private final Set<ByteBuffer> activeBuffers = Sets.newIdentityHashSet();
+
+    @Override
+    public ByteBuffer acquireByteBuffer(int capacity) {
+      synchronized (freeBuffers) {
+        ByteBuffer buffer = freeBuffers.pollFirst();
+        // Ensure the buffer has sufficient capacity, eg, skip buffers that are too small.
+        if (buffer != null && buffer.capacity() < capacity) {
+          List<ByteBuffer> small = new ArrayList<>(freeBuffers.size());
+          do {
+            small.add(buffer);
+            buffer = freeBuffers.pollFirst();
+          } while (buffer != null && buffer.capacity() < capacity);
+          freeBuffers.addAll(small);
+        }
+        if (buffer == null) {
+          buffer = ByteBuffer.allocate(capacity);
+        }
+        assert !activeBuffers.contains(buffer);
+        activeBuffers.add(buffer);
+        return buffer;
+      }
+    }
+
+    @Override
+    public void releaseByteBuffer(ByteBuffer buffer) {
+      synchronized (freeBuffers) {
+        assert activeBuffers.contains(buffer);
+        activeBuffers.remove(buffer);
+        buffer.position(0);
+        freeBuffers.offerFirst(buffer);
+      }
+    }
+
     @Override
     public void accept(
         String primaryClassDescriptor,
-        byte[] data,
+        ByteDataView data,
         Set<String> descriptors,
         DiagnosticsHandler handler) {
       addDescriptors(descriptors);
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
index ba43a10..e6ec726 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DexIndexedConsumer;
@@ -29,7 +30,7 @@
     ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(originalJar);
     for (Class clazz : PhiDefinitionsTest.CLASSES) {
       String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
-      consumer.accept(ToolHelper.getClassAsBytes(clazz), descriptor, null);
+      consumer.accept(ByteDataView.of(ToolHelper.getClassAsBytes(clazz)), descriptor, null);
     }
     consumer.finished(null);
     runOriginalJar(originalJar);
@@ -56,9 +57,9 @@
     Path dumpJar = temp.getRoot().toPath().resolve("dump.jar");
     ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(dumpJar);
     String desc = 'L' + PhiDefinitionsTestDump.INTERNAL_NAME + ';';
-    consumer.accept(PhiDefinitionsTestDump.dump(), desc, null);
+    consumer.accept(ByteDataView.of(PhiDefinitionsTestDump.dump()), desc, null);
     String innerDesc = 'L' + PhiDefinitionsTestDump.INNER_INTERNAL_NAME + ';';
-    consumer.accept(PhiDefinitionsTestDump.dumpInner(), innerDesc, null);
+    consumer.accept(ByteDataView.of(PhiDefinitionsTestDump.dumpInner()), innerDesc, null);
     consumer.finished(null);
     runOriginalJar(dumpJar);
     return dumpJar;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index e727904..d5b88cb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -62,7 +62,7 @@
   @Test
   public void testWriteOnlyField_putObject_gone() throws Exception {
     List<Path> processedApp = runR8(EXAMPLE_KEEP);
-    CodeInspector inspector = new CodeInspector(processedApp, null, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
     clazz.forAllMethods(
         methodSubject -> {
@@ -77,7 +77,7 @@
   @Test
   public void testWriteOnlyField_dontoptimize() throws Exception {
     List<Path> processedApp = runR8(DONT_OPTIMIZE);
-    CodeInspector inspector = new CodeInspector(processedApp, null, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
     assert backend == Backend.DEX || backend == Backend.CF;
     clazz.forAllMethods(
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 9c15ce7..97d0de1 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.getPathFromDescriptor;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.ApplicationReader;
@@ -395,7 +396,7 @@
   public void writeClassFiles(ClassFileConsumer consumer, DiagnosticsHandler handler)
       throws Exception {
     for (ClassBuilder clazz : classes) {
-      consumer.accept(compile(clazz), clazz.getDescriptor(), handler);
+      consumer.accept(ByteDataView.of(compile(clazz)), clazz.getDescriptor(), handler);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
index 4903304..517bd03 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
@@ -93,7 +93,7 @@
       options.enableInlining = false;
       options.enableClassMerging = false;
     });
-    CodeInspector codeInspector = new CodeInspector(processedApp, o -> o.enableCfFrontend = true);
+    CodeInspector codeInspector = new CodeInspector(processedApp);
     ClassSubject classSubject = codeInspector.clazz(TestMain.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(CodeInspector.MAIN);
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index bf63d70..6fccb9a 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -388,7 +388,7 @@
       originalInspection.accept(inspector);
     }
 
-    CodeInspector inspector = new CodeInspector(processed, null, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(processed);
     inspection.accept(inspector);
 
     // We don't run Art, as the test R8RunExamplesTest already does that.
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 6dd02c6..a66c222 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -62,12 +62,7 @@
               // setting (5) is just too small.
               options.inliningInstructionLimit = 10;
             });
-    inspection.accept(
-        new CodeInspector(
-            app,
-            options -> {
-              options.enableCfFrontend = true;
-            }));
+    inspection.accept(new CodeInspector(app));
 
     if (backend == Backend.DEX) {
       // Run on Art to check generated code against verifier.
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
index aaecd58..ee25918 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -213,11 +213,7 @@
 
   private static CodeInspector createDexInspector(AndroidApp outputApp)
       throws IOException, ExecutionException {
-    return new CodeInspector(
-        outputApp,
-        o -> {
-          o.enableCfFrontend = true;
-        });
+    return new CodeInspector(outputApp);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 73cdaaf..ba7d491 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -17,8 +17,8 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
@@ -89,12 +89,7 @@
 
   @Test
   public void identiferMinifierTest() throws Exception {
-    CodeInspector codeInspector =
-        new CodeInspector(
-            processedApp,
-            options -> {
-              options.enableCfFrontend = true;
-            });
+    CodeInspector codeInspector = new CodeInspector(processedApp);
     inspection.accept(codeInspector);
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
index 56dce3a..7c25445 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -39,7 +40,7 @@
     ArchiveConsumer buildInput = new ArchiveConsumer(inputJar);
     for (Class clazz : CLASSES) {
       buildInput.accept(
-          ToolHelper.getClassAsBytes(clazz),
+          ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
           DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
           null);
     }
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index f401f6a..e6589d5a 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -72,13 +72,7 @@
         Origin.unknown());
     AndroidApp app =
         ToolHelper.runR8(builder.build(), opts -> opts.enableClassInlining = enableClassInliner);
-    inspection.accept(
-        new CodeInspector(
-            app,
-            options -> {
-              options.enableCfFrontend = true;
-            }),
-        mode);
+    inspection.accept(new CodeInspector(app), mode);
 
     if (backend == Backend.DEX) {
       // Run on Art to check generated code against verifier.
diff --git a/src/test/java/com/android/tools/r8/regress/Regress37740372.java b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
index e6ac9f8..34e4f3e 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress37740372.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.D8Command.Builder;
@@ -142,9 +143,9 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       assertEquals(0, fileIndex);
-      this.data = data;
+      this.data = data.copyByteData();
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 911ceb7..a09b9a4 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -59,7 +59,7 @@
           .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     }
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
-    CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(app);
     List<FoundClassSubject> classes = inspector.allClasses();
 
     // Check that the synthetic class is still present.
@@ -103,7 +103,7 @@
           .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     }
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
-    CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(app);
     List<FoundClassSubject> classes = inspector.allClasses();
 
     // Check that the synthetic class is still present.
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java
index fa4e3e1..2bc0dc6 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.regress.b78493232;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.ToolHelper;
@@ -30,9 +31,10 @@
         ? Paths.get(args[0])
         : Paths.get("Regress78493232.jar");
     ArchiveConsumer consumer = new ArchiveConsumer(output);
-    consumer.accept(Regress78493232Dump.dump(), Regress78493232Dump.CLASS_DESC, null);
     consumer.accept(
-        ToolHelper.getClassAsBytes(Regress78493232Utils.class),
+        ByteDataView.of(Regress78493232Dump.dump()), Regress78493232Dump.CLASS_DESC, null);
+    consumer.accept(
+        ByteDataView.of(ToolHelper.getClassAsBytes(Regress78493232Utils.class)),
         Regress78493232Dump.UTILS_CLASS_DESC,
         null);
     consumer.finished(null);
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
index ee99b38..bfc8154 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -61,7 +61,7 @@
             .setProgramConsumer(emptyConsumer(backend))
             .build();
     AndroidApp result = ToolHelper.runR8(command);
-    CodeInspector inspector = new CodeInspector(result, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(result);
     Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
index 12ef651..c7ab1b7 100644
--- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -35,7 +36,7 @@
     ArchiveConsumer buildInput = new ArchiveConsumer(inputJar);
     for (Class clazz : CLASSES) {
       buildInput.accept(
-          ToolHelper.getClassAsBytes(clazz),
+          ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
           DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
           null);
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index f79e01f..a690ee8 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -417,12 +417,14 @@
         assertTrue(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertEquals(rule.getName().matches("returnsTrue"), rule.getReturnValue().getBoolean());
         matches |= 1 << 0;
       } else if (rule.getName().matches("returns1")) {
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertTrue(rule.getReturnValue().isSingleValue());
         assertEquals(1, rule.getReturnValue().getValueRange().getMin());
         assertEquals(1, rule.getReturnValue().getValueRange().getMax());
@@ -432,6 +434,7 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertFalse(rule.getReturnValue().isSingleValue());
         assertEquals(2, rule.getReturnValue().getValueRange().getMin());
         assertEquals(4, rule.getReturnValue().getValueRange().getMax());
@@ -440,6 +443,7 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertFalse(rule.getReturnValue().isSingleValue());
         assertEquals(234, rule.getReturnValue().getValueRange().getMin());
         assertEquals(567, rule.getReturnValue().getValueRange().getMax());
@@ -448,15 +452,23 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertTrue(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertEquals("com.google.C", rule.getReturnValue().getField().clazz.toString());
         assertEquals("int", rule.getReturnValue().getField().type.toString());
         assertEquals("X", rule.getReturnValue().getField().name.toString());
         matches |= 1 << 4;
+      } else if (rule.getName().matches("returnsNull")) {
+        assertFalse(rule.getReturnValue().isBoolean());
+        assertFalse(rule.getReturnValue().isValueRange());
+        assertFalse(rule.getReturnValue().isField());
+        assertTrue(rule.getReturnValue().isNull());
+        assertTrue(rule.getReturnValue().isSingleValue());
+        matches |= 1 << 5;
       } else {
         fail("Unexpected");
       }
     }
-    assertEquals((1 << 5) - 1, matches);
+    assertEquals((1 << 6) - 1, matches);
   }
 
   @Test
@@ -474,12 +486,14 @@
         assertTrue(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertEquals(rule.getName().matches("isTrue"), rule.getReturnValue().getBoolean());
         matches |= 1 << 0;
       } else if (rule.getName().matches("is1")) {
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertTrue(rule.getReturnValue().isSingleValue());
         assertEquals(1, rule.getReturnValue().getValueRange().getMin());
         assertEquals(1, rule.getReturnValue().getValueRange().getMax());
@@ -489,6 +503,7 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertFalse(rule.getReturnValue().isSingleValue());
         assertEquals(2, rule.getReturnValue().getValueRange().getMin());
         assertEquals(4, rule.getReturnValue().getValueRange().getMax());
@@ -497,6 +512,7 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertTrue(rule.getReturnValue().isValueRange());
         assertFalse(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertFalse(rule.getReturnValue().isSingleValue());
         assertEquals(234, rule.getReturnValue().getValueRange().getMin());
         assertEquals(567, rule.getReturnValue().getValueRange().getMax());
@@ -505,15 +521,23 @@
         assertFalse(rule.getReturnValue().isBoolean());
         assertFalse(rule.getReturnValue().isValueRange());
         assertTrue(rule.getReturnValue().isField());
+        assertFalse(rule.getReturnValue().isNull());
         assertEquals("com.google.C", rule.getReturnValue().getField().clazz.toString());
         assertEquals("int", rule.getReturnValue().getField().type.toString());
         assertEquals("X", rule.getReturnValue().getField().name.toString());
         matches |= 1 << 4;
+      } else if (rule.getName().matches("isNull")) {
+        assertFalse(rule.getReturnValue().isBoolean());
+        assertFalse(rule.getReturnValue().isValueRange());
+        assertFalse(rule.getReturnValue().isField());
+        assertTrue(rule.getReturnValue().isNull());
+        assertTrue(rule.getReturnValue().isSingleValue());
+        matches |= 1 << 5;
       } else {
         fail("Unexpected");
       }
     }
-    assertEquals((1 << 5) - 1, matches);
+    assertEquals((1 << 6) - 1, matches);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
index ba0d468..1197c31 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
@@ -125,9 +126,11 @@
     Path inputJar = temp.getRoot().toPath().resolve("input.jar");
     ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
     consumer.accept(
-        downgradeClass(ToolHelper.getClassAsBytes(Base.class), OLD_VERSION), BASE_DESCRIPTOR, null);
+        ByteDataView.of(downgradeClass(ToolHelper.getClassAsBytes(Base.class), OLD_VERSION)),
+        BASE_DESCRIPTOR,
+        null);
     consumer.accept(
-        ToolHelper.getClassAsBytes(Inlinee.class),
+        ByteDataView.of(ToolHelper.getClassAsBytes(Inlinee.class)),
         DescriptorUtils.javaTypeToDescriptor(Inlinee.class.getName()),
         null);
     consumer.finished(null);
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
new file mode 100644
index 0000000..578ad2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.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;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+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 TreeShakingAssumenosideeffects6Test extends TreeShakingTest {
+
+  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  public static Collection<Object[]> data() {
+    List<Object[]> parameters = new ArrayList<>();
+    for (MinifyMode minify : MinifyMode.values()) {
+      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+    }
+    return parameters;
+  }
+
+  public TreeShakingAssumenosideeffects6Test(
+      Frontend frontend, Backend backend, MinifyMode minify) {
+    super(
+        "examples/assumenosideeffects6",
+        "assumenosideeffects6.Assumenosideeffects",
+        frontend,
+        backend,
+        minify);
+  }
+
+  @Test
+  public void testKeeprules() throws Exception {
+    runTest(
+        null,
+        TreeShakingAssumenosideeffects6Test::assumenosideeffects6CheckOutput,
+        null,
+        ImmutableList.of("src/test/examples/assumenosideeffects6/keep-rules.txt"));
+  }
+
+  @Test
+  public void testKeeprulesdiscard() throws Exception {
+    runTest(
+        null,
+        TreeShakingAssumenosideeffects6Test::assumenosideeffects6CheckOutput,
+        null,
+        ImmutableList.of("src/test/examples/assumenosideeffects6/keep-rules-discard.txt"));
+  }
+
+  private static void assumenosideeffects6CheckOutput(String output1, String output2) {
+    Assert.assertEquals(
+        StringUtils.lines("methodStaticNull", "NOT NULL", "methodNull", "NOT NULL"), output1);
+    Assert.assertEquals(StringUtils.lines("NULL", "NULL"), output2);
+  }
+}
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
new file mode 100644
index 0000000..789e40c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.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;
+import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+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 TreeShakingAssumevalues7Test extends TreeShakingTest {
+
+  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  public static Collection<Object[]> data() {
+    List<Object[]> parameters = new ArrayList<>();
+    for (MinifyMode minify : MinifyMode.values()) {
+      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+    }
+    return parameters;
+  }
+
+  public TreeShakingAssumevalues7Test(Frontend frontend, Backend backend, MinifyMode minify) {
+    super("examples/assumevalues7", "assumevalues7.Assumevalues", frontend, backend, minify);
+  }
+
+  @Test
+  public void test() throws Exception {
+    runTest(
+        getBackend() == Backend.DEX ? TreeShakingAssumevalues7Test::assumevalues7CheckCode : null,
+        TreeShakingAssumevalues7Test::assumevalues7CheckOutput,
+        null,
+        ImmutableList.of("src/test/examples/assumevalues7/keep-rules.txt"));
+  }
+
+  private static void assumevalues7CheckCode(CodeInspector inspector) {
+    inspector.forAllClasses(c -> {
+      c.forAllMethods(m -> {
+        if (m.getFinalName().equals("main")) {
+          m.iterateInstructions().forEachRemaining(i -> {
+            if (i.isConstString(JumboStringMode.ALLOW)) {
+              ConstStringInstructionSubject str = (ConstStringInstructionSubject) i;
+              assert !str.getString().toASCIIString().contains("NOPE");
+            }
+          });
+        }
+      });
+    });
+  }
+
+  private static void assumevalues7CheckOutput(String output1, String output2) {
+    Assert.assertEquals(StringUtils.lines("NOPE_STATIC_NOT_NULL", "NOPE_NOT_NULL", "OK"), output1);
+    Assert.assertEquals(StringUtils.lines("OK"), output2);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index b3e6367..004acc1 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -78,7 +78,7 @@
           .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     }
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
-    inspection.accept(new CodeInspector(app, o -> o.enableCfFrontend = true));
+    inspection.accept(new CodeInspector(app));
     String result = backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass);
     if (ToolHelper.isWindows()) {
       result = result.replace(System.lineSeparator(), "\n");
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index d12f41a..f37d6c6 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -61,7 +61,7 @@
             .addLibraryFiles(library);
     ToolHelper.allowTestProguardOptions(builder);
     builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
-    return new CodeInspector(ToolHelper.runR8(builder.build(), o -> o.enableCfFrontend = true));
+    return new CodeInspector(ToolHelper.runR8(builder.build()));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index ac46dfc..fb61a57 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.ApplicationReader;
@@ -132,8 +133,8 @@
 
     @Override
     public void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
-      contents = data;
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+      contents = data.copyByteData();
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 6fbbb20..bc31615 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -79,6 +79,7 @@
     }
     Timing timing = new Timing("CodeInspector");
     InternalOptions options = new InternalOptions();
+    options.enableCfFrontend = true;
     if (optionsConsumer != null) {
       optionsConsumer.accept(options);
     }
@@ -89,7 +90,7 @@
 
   public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
     this(
-        new ApplicationReader(app, new InternalOptions(), new Timing("CodeInspector"))
+        new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
             .read(app.getProguardMapOutputData()));
   }
 
@@ -102,13 +103,16 @@
 
   private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
     InternalOptions internalOptions = new InternalOptions();
-    optionsConsumer.accept(internalOptions);
+    internalOptions.enableCfFrontend = true;
+    if (optionsConsumer != null) {
+      optionsConsumer.accept(internalOptions);
+    }
     return internalOptions;
   }
 
   public CodeInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
     this(
-        new ApplicationReader(app, new InternalOptions(), new Timing("CodeInspector"))
+        new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
             .read(StringResource.fromFile(proguardMap)));
   }
 
diff --git a/src/test/proguard/valid/assume-no-side-effects-with-return-value.flags b/src/test/proguard/valid/assume-no-side-effects-with-return-value.flags
index 78d3683..b233ea3 100644
--- a/src/test/proguard/valid/assume-no-side-effects-with-return-value.flags
+++ b/src/test/proguard/valid/assume-no-side-effects-with-return-value.flags
@@ -9,4 +9,5 @@
     public static int returns2To4() return 2..4;
     public static int returns234To567() return 234..567;
     public static int returnsField() return com.google.C.X;
+    public static Object returnsNull() return null;
 }
diff --git a/src/test/proguard/valid/assume-values-with-return-value.flags b/src/test/proguard/valid/assume-values-with-return-value.flags
index 7f89a79..90cefbe 100644
--- a/src/test/proguard/valid/assume-values-with-return-value.flags
+++ b/src/test/proguard/valid/assume-values-with-return-value.flags
@@ -9,4 +9,5 @@
     public static final int is2To4() return 2..4;
     public static final int is234To567() return 234..567;
     public static final int isField() return com.google.C.X;
+    public static final Object isNull() return null;
 }
\ No newline at end of file