Merge "Also rematerialize constant string values"
diff --git a/.gitignore b/.gitignore
index cdc19a1..41e22c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,8 +46,6 @@
 third_party/jdwp-tests
 third_party/kotlin.tar.gz
 third_party/kotlin
-third_party/shadow
-third_party/shadow.tar.gz
 third_party/photos/*
 !third_party/photos/*.sha1
 third_party/proguard/*
diff --git a/build.gradle b/build.gradle
index ff22c66..f5d54ee 100644
--- a/build.gradle
+++ b/build.gradle
@@ -85,11 +85,7 @@
     dependencies {
         classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1'
         classpath 'com.cookpad.android.licensetools:license-tools-plugin:0.23.0'
-        // TODO(ager): shadow does not support java9 class files yet. Once it does,
-        // we should use the offial version instead of our fork using ASM 6.0 to
-        // support java9.
-        // classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
-        classpath files("third_party/shadow/shadow-2.0.1.jar")
+        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.3'
         classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
         classpath "net.ltgt.gradle:gradle-apt-plugin:0.12"
     }
@@ -307,7 +303,6 @@
                 "jctf",
                 "kotlin",
                 "android_cts_baseline",
-                "shadow",
                 "ddmlib",
                 "core-lambda-stubs",
                 "openjdk/openjdk-rt-1.8",
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 d5a7013..7f9f13f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -369,20 +369,24 @@
    * If run multiple times on a class, the lowest index that is required to be a JumboString will
    * be used.
    */
-  private static void rewriteCodeWithJumboStrings(ObjectToOffsetMapping mapping,
+  private void rewriteCodeWithJumboStrings(ObjectToOffsetMapping mapping,
       Collection<DexProgramClass> classes, DexApplication application) {
-    // If there are no strings with jumbo indices at all this is a no-op.
-    if (!mapping.hasJumboStrings()) {
-      return;
-    }
-    // If the globally highest sorting string is not a jumbo string this is also a no-op.
-    if (application.highestSortingString != null &&
-        application.highestSortingString.slowCompareTo(mapping.getFirstJumboString()) < 0) {
-      return;
+    // Do not bail out early if forcing jumbo string processing.
+    if (!options.testing.forceJumboStringProcessing) {
+      // If there are no strings with jumbo indices at all this is a no-op.
+      if (!mapping.hasJumboStrings()) {
+        return;
+      }
+      // If the globally highest sorting string is not a jumbo string this is also a no-op.
+      if (application.highestSortingString != null &&
+          application.highestSortingString.slowCompareTo(mapping.getFirstJumboString()) < 0) {
+        return;
+      }
     }
     // At least one method needs a jumbo string.
     for (DexProgramClass clazz : classes) {
-      clazz.forEachMethod(method -> method.rewriteCodeWithJumboStrings(mapping, application));
+      clazz.forEachMethod(method -> method.rewriteCodeWithJumboStrings(
+          mapping, application, options.testing.forceJumboStringProcessing));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index c96a31a..519bd58 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -226,7 +226,7 @@
           lastOriginalOffset += advance.delta;
           Instruction target = debugEventTargets.get(lastOriginalOffset);
           int pcDelta = target.getOffset() - lastNewOffset;
-          addAdvancementEvents(0, pcDelta, events);
+          events.add(factory.createAdvancePC(pcDelta));
           lastNewOffset = target.getOffset();
         } else if (event instanceof Default) {
           Default defaultEvent = (Default) event;
@@ -234,7 +234,7 @@
           Instruction target = debugEventTargets.get(lastOriginalOffset);
           int lineDelta = defaultEvent.getLineDelta();
           int pcDelta = target.getOffset() - lastNewOffset;
-          addAdvancementEvents(lineDelta, pcDelta, events);
+          addDefaultEvent(lineDelta, pcDelta, events);
           lastNewOffset = target.getOffset();
         } else {
           events.add(event);
@@ -248,21 +248,19 @@
     return code.getDebugInfo();
   }
 
-  private void addAdvancementEvents(int lineDelta, int pcDelta, List<DexDebugEvent> events) {
+  // Add a default event. If the lineDelta and pcDelta can be encoded in one default event
+  // that will be done. Otherwise, this can output an advance line and/or advance pc event
+  // followed by a default event. A default event is always emitted as that is what will
+  // materialize an entry in the line table.
+  private void addDefaultEvent(int lineDelta, int pcDelta, List<DexDebugEvent> events) {
     if (lineDelta < Constants.DBG_LINE_BASE
         || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
       events.add(factory.createAdvanceLine(lineDelta));
       lineDelta = 0;
-      if (pcDelta == 0) {
-        return;
-      }
     }
     if (pcDelta >= Constants.DBG_ADDRESS_RANGE) {
       events.add(factory.createAdvancePC(pcDelta));
       pcDelta = 0;
-      if (lineDelta == 0) {
-        return;
-      }
     }
     int specialOpcode =
         0x0a + (lineDelta - Constants.DBG_LINE_BASE) + Constants.DBG_LINE_RANGE * pcDelta;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index e18bf8a..d437180 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -502,19 +502,23 @@
    * was rewritten to avoid rewriting again unless needed.
    */
   public synchronized void rewriteCodeWithJumboStrings(ObjectToOffsetMapping mapping,
-      DexApplication application) {
+      DexApplication application, boolean force) {
     assert code == null || code.isDexCode();
     if (code == null) {
       return;
     }
     DexCode code = this.code.asDexCode();
-    if (code.highestSortingString != null) {
-      if (mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
-        JumboStringRewriter rewriter =
-            new JumboStringRewriter(this, mapping.getFirstJumboString(),
-                application.dexItemFactory);
-        rewriter.rewrite();
-      }
+    DexString firstJumboString = null;
+    if (force) {
+      firstJumboString = mapping.getFirstString();
+    } else if (code.highestSortingString != null
+        && mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
+      firstJumboString = mapping.getFirstJumboString();
+    }
+    if (firstJumboString != null) {
+      JumboStringRewriter rewriter =
+          new JumboStringRewriter(this, firstJumboString, application.dexItemFactory);
+      rewriter.rewrite();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index b3be107..a8b431c 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap.Entry;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.Collection;
 import java.util.Collections;
@@ -197,6 +198,15 @@
     return firstJumboString;
   }
 
+  public DexString getFirstString() {
+    for (Entry<DexString> dexStringEntry : strings.reference2IntEntrySet()) {
+      if (dexStringEntry.getIntValue() == 0) {
+        return dexStringEntry.getKey();
+      }
+    }
+    return null;
+  }
+
   private <T extends IndexedDexItem> int getOffsetFor(T item, Reference2IntMap<T> map) {
     int index = map.getInt(item);
     assert index != NOT_SET : "Index was not set: " + item;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 16050b2..f49962c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2478,70 +2478,40 @@
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
 
     Value openParenthesis = addConstString(code, iterator, "(");
-    Value comma = addConstString(code, iterator, ",");
+    Value comma = addConstString(code, iterator, ", ");
     Value closeParenthesis = addConstString(code, iterator, ")");
-    Value indent = addConstString(code, iterator, "  ");
-    Value nul = addConstString(code, iterator, "(null)");
-    Value primitive = addConstString(code, iterator, "(primitive)");
-    Value empty = addConstString(code, iterator, "");
 
-    iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, openParenthesis)));
+    iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, openParenthesis)));
     for (int i = 0; i < arguments.size(); i++) {
-      iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, indent)));
-
-      // Add a block for end-of-line printing.
-      BasicBlock eol = BasicBlock.createGotoBlock(code.blocks.size());
-      code.blocks.add(eol);
-
-      BasicBlock successor = block.unlinkSingleSuccessor();
-      block.link(eol);
-      eol.link(successor);
-
+      if (i != 0) {
+        iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, comma)));
+      }
       Value argument = arguments.get(i);
-      if (argument.outType() != ValueType.OBJECT) {
-        iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, primitive)));
+      if (i == 0 && !method.isStaticMethod()) {
+        Value thisString = addConstString(code, iterator, "this");
+        iterator.add(createPrint(out, dexItemFactory.stringType, thisString));
       } else {
-        // Insert "if (argument != null) ...".
-        successor = block.unlinkSingleSuccessor();
-        If theIf = new If(If.Type.NE, argument);
-        theIf.setPosition(position);
-        BasicBlock ifBlock = BasicBlock.createIfBlock(code.blocks.size(), theIf);
-        code.blocks.add(ifBlock);
-        // Fallthrough block must be added right after the if.
-        BasicBlock isNullBlock = BasicBlock.createGotoBlock(code.blocks.size());
-        code.blocks.add(isNullBlock);
-        BasicBlock isNotNullBlock = BasicBlock.createGotoBlock(code.blocks.size());
-        code.blocks.add(isNotNullBlock);
-
-        // Link the added blocks together.
-        block.link(ifBlock);
-        ifBlock.link(isNotNullBlock);
-        ifBlock.link(isNullBlock);
-        isNotNullBlock.link(successor);
-        isNullBlock.link(successor);
-
-        // Fill code into the blocks.
-        iterator = isNullBlock.listIterator();
-        iterator.setInsertionPosition(position);
-        iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
-        iterator = isNotNullBlock.listIterator();
-        iterator.setInsertionPosition(position);
-        value = code.createValue(ValueType.OBJECT);
-        iterator.add(new InvokeVirtual(dexItemFactory.objectMethods.getClass, value,
-            ImmutableList.of(arguments.get(i))));
-        iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
+        int formalIndex = method.isStaticMethod() ? i : i - 1;
+        DexType argumentType = method.method.proto.parameters.values[formalIndex];
+        iterator.add(createPrint(out, argumentType, argument));
       }
-
-      iterator = eol.listIterator();
-      iterator.setInsertionPosition(position);
-      if (i == arguments.size() - 1) {
-        iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, closeParenthesis)));
-      } else {
-        iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, comma)));
-      }
-      block = eol;
     }
-    // When we fall out of the loop the iterator is in the last eol block.
-    iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, empty)));
+    iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, closeParenthesis)));
+  }
+
+  private Instruction createPrint(Value printStream, DexType argumentType, Value argumentValue) {
+    if (argumentType.isPrimitiveType()) {
+      if (argumentType != dexItemFactory.longType
+          && argumentType != dexItemFactory.doubleType
+          && argumentType != dexItemFactory.floatType) {
+        argumentType = dexItemFactory.intType;
+      }
+    } else {
+      argumentType = dexItemFactory.objectType;
+    }
+    DexType javaIoPrintStreamType = dexItemFactory.createType("Ljava/io/PrintStream;");
+    DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, argumentType);
+    DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
+    return new InvokeVirtual(print, null, ImmutableList.of(printStream, argumentValue));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 4099912..a740a03 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -412,6 +412,7 @@
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
+    public boolean forceJumboStringProcessing = false;
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
new file mode 100644
index 0000000..0f92fb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
@@ -0,0 +1,156 @@
+// 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.cf;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.base.Charsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class BootstrapTest extends TestBase {
+
+  private static final Path R8_STABLE_JAR = Paths.get("third_party/r8/r8.jar");
+
+  private static final String R8_NAME = "com.android.tools.r8.R8";
+  private static final String[] KEEP_R8 = {
+      "-keep public class " + R8_NAME + " {",
+      "  public static void main(...);",
+      "}",
+  };
+
+  private static final String HELLO_NAME = "hello.Hello";
+  private static final String[] KEEP_HELLO = {
+      "-keep class " + HELLO_NAME + " {",
+      "  public static void main(...);",
+      "}",
+  };
+
+  private class R8Result {
+
+    final ProcessResult processResult;
+    final Path outputJar;
+    final String pgMap;
+
+    R8Result(ProcessResult processResult, Path outputJar, String pgMap) {
+      this.processResult = processResult;
+      this.outputJar = outputJar;
+      this.pgMap = pgMap;
+    }
+
+    @Override
+    public String toString() {
+      // TODO(mathiasr): Add pgMap to output when resource API (go/r8g/19460) has landed.
+      // Without resource API, comparing pgMaps will fail because R8 does not keep the resource
+      // indicating which Git revision R8 was compiled from.
+      return processResult.toString();
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+    // Run hello.jar to ensure it exists and is valid.
+    Path hello = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
+    ProcessResult runHello = ToolHelper.runJava(hello, "hello.Hello");
+    assertEquals(0, runHello.exitCode);
+
+    // Run r8.jar on hello.jar to ensure that r8.jar is a working compiler.
+    R8Result runInputR8 = runExternalR8(R8_STABLE_JAR, hello, "input", KEEP_HELLO);
+    ProcessResult runHelloR8 = ToolHelper.runJava(runInputR8.outputJar, "hello.Hello");
+    assertEquals(runHello.toString(), runHelloR8.toString());
+
+    // Run R8 on r8.jar, and run the resulting compiler on hello.jar.
+    Path output = runR8(R8_STABLE_JAR, "r8-r8", KEEP_R8);
+    R8Result runR8R8 = runExternalR8(output, hello, "output", KEEP_HELLO);
+    // Check that the process outputs (exit code, stdout, stderr) are the same.
+    assertEquals(runInputR8.toString(), runR8R8.toString());
+    // Check that the output jars are the same.
+    assertProgramsEqual(runInputR8.outputJar, runR8R8.outputJar);
+  }
+
+  private Path runR8(Path inputJar, String outputFolder, String[] keepRules) throws Exception {
+    Path outputPath = temp.newFolder(outputFolder).toPath();
+    Path outputJar = outputPath.resolve("output.jar");
+    Path pgConfigFile = outputPath.resolve("keep.rules");
+    FileUtils.writeTextFile(pgConfigFile, keepRules);
+    ToolHelper.runR8(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            // TODO(mathiasr): Add resources to output when resource API (go/r8g/19460) has landed.
+            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar))
+            .addProgramFiles(inputJar)
+            .addProguardConfigurationFiles(pgConfigFile)
+            .build());
+    return outputJar;
+  }
+
+  private R8Result runExternalR8(Path r8Jar, Path inputJar, String outputFolder, String[] keepRules)
+      throws Exception {
+    Path outputPath = temp.newFolder(outputFolder).toPath();
+    Path pgConfigFile = outputPath.resolve("keep.rules");
+    Path outputJar = outputPath.resolve("output.jar");
+    Path pgMapFile = outputPath.resolve("map.txt");
+    FileUtils.writeTextFile(pgConfigFile, keepRules);
+    ProcessResult processResult =
+        ToolHelper.runJava(
+            r8Jar,
+            R8_NAME,
+            "--lib",
+            ToolHelper.JAVA_8_RUNTIME,
+            "--classfile",
+            inputJar.toString(),
+            "--output",
+            outputJar.toString(),
+            "--pg-conf",
+            pgConfigFile.toString(),
+            "--debug",
+            "--pg-map-output",
+            pgMapFile.toString());
+    if (processResult.exitCode != 0) {
+      System.out.println(processResult);
+    }
+    assertEquals(0, processResult.exitCode);
+    String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
+    return new R8Result(processResult, outputJar, pgMap);
+  }
+
+  private static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
+    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
+    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
+    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
+    for (String descriptor : expected.getClassDescriptors()) {
+      assertArrayEquals(
+          "Class " + descriptor + " differs",
+          getClassAsBytes(expected, descriptor),
+          getClassAsBytes(actual, descriptor));
+    }
+  }
+
+  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+    Collections.sort(descriptorList);
+    return descriptorList;
+  }
+
+  private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+      throws Exception {
+    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index 6432530..f2e96b3 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -27,16 +27,13 @@
 
   private static final String SOURCE_FILE = "Inlining1.java";
 
-  private static DebugTestConfig makeConfig(
-      OutputMode outputMode,
+  private DebugTestConfig makeConfig(
       LineNumberOptimization lineNumberOptimization,
       boolean writeProguardMap)
       throws Exception {
-    assert outputMode == OutputMode.DexIndexed || outputMode == OutputMode.ClassFile;
     AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
-    String prefix = outputMode == OutputMode.ClassFile ? "cf" : "dex";
     Path outdir = temp.newFolder().toPath();
-    Path outjar = outdir.resolve(prefix + "_r8_compiled.jar");
+    Path outjar = outdir.resolve("dex_r8_compiled.jar");
     Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
     R8Command.Builder builder =
         R8Command.builder()
@@ -44,29 +41,29 @@
             .setMinApiLevel(minSdk.getLevel())
             .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
             .setMode(CompilationMode.RELEASE)
-            .setOutput(outjar, outputMode);
+            .setOutput(outjar, OutputMode.DexIndexed);
     if (proguardMapPath != null) {
       builder.setProguardMapOutputPath(proguardMapPath);
     }
     ToolHelper.runR8(
-        builder.build(), options -> options.lineNumberOptimization = lineNumberOptimization);
-    DebugTestConfig config =
-        outputMode == OutputMode.ClassFile
-            ? new CfDebugTestConfig(outjar)
-            : new DexDebugTestConfig(outjar);
+        builder.build(), options -> {
+          options.lineNumberOptimization = lineNumberOptimization;
+          options.testing.forceJumboStringProcessing = forceJumboStringProcessing;
+        });
+    DebugTestConfig config = new DexDebugTestConfig(outjar);
     config.setProguardMap(proguardMapPath);
     return config;
   }
 
-  private OutputMode outputMode;
+  private boolean forceJumboStringProcessing;
 
-  @Parameters(name = "{0}")
-  public static Collection<OutputMode> data() {
-    return Arrays.asList(OutputMode.DexIndexed);
+  @Parameters(name="forceJumbo: {0}")
+  public static Collection<Boolean> data() {
+    return Arrays.asList(true, false);
   }
 
-  public DebugInfoWhenInliningTest(OutputMode outputMode) {
-    this.outputMode = outputMode;
+  public DebugInfoWhenInliningTest(boolean forceJumboStringProcessing) {
+    this.forceJumboStringProcessing = forceJumboStringProcessing;
   }
 
   @Test
@@ -78,7 +75,7 @@
     // (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
     // are emitted duplicated in the dex code, the debugger stops only when there's a change.
     int[] lineNumbers = {7, 32, 11, 7};
-    testEachLine(makeConfig(outputMode, LineNumberOptimization.OFF, false), lineNumbers);
+    testEachLine(makeConfig(LineNumberOptimization.OFF, false), lineNumbers);
   }
 
   @Test
@@ -90,13 +87,13 @@
     // (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
     // are emitted duplicated in the dex code, the debugger stops only when there's a change.
     int[] lineNumbers = {7, 32, 11, 7};
-    testEachLine(makeConfig(outputMode, LineNumberOptimization.OFF, true), lineNumbers);
+    testEachLine(makeConfig(LineNumberOptimization.OFF, true), lineNumbers);
   }
 
   @Test
   public void testEachLineOptimized() throws Throwable {
     int[] lineNumbers = {1, 2, 3, 4, 5, 6, 7, 8};
-    testEachLine(makeConfig(outputMode, LineNumberOptimization.ON, false), lineNumbers);
+    testEachLine(makeConfig(LineNumberOptimization.ON, false), lineNumbers);
   }
 
   @Test
@@ -133,7 +130,7 @@
                 new SignatureAndLine("void Inlining2.differentFileMultilevelInliningLevel1()", 36),
                 new SignatureAndLine("void main(java.lang.String[])", 26)));
     testEachLine(
-        makeConfig(outputMode, LineNumberOptimization.ON, true), lineNumbers, inlineFramesList);
+        makeConfig(LineNumberOptimization.ON, true), lineNumbers, inlineFramesList);
   }
 
   private void testEachLine(DebugTestConfig config, int[] lineNumbers) throws Throwable {
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
new file mode 100644
index 0000000..abe889e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java
@@ -0,0 +1,21 @@
+// 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.regress.b78493232;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import org.junit.Test;
+
+public class Regress78493232 extends AsmTestBase {
+
+  @Test
+  public void test() throws Exception {
+    // Run test on JVM and ART(x86) to ensure expected behavior.
+    // Running the same test on an ARM JIT causes errors.
+    ensureSameOutput(
+        Regress78493232Dump.CLASS_NAME,
+        Regress78493232Dump.dump(),
+        ToolHelper.getClassAsBytes(Regress78493232Utils.class));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump.java
new file mode 100644
index 0000000..ba7ec6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump.java
@@ -0,0 +1,376 @@
+// 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.regress.b78493232;
+
+import com.android.tools.r8.utils.DescriptorUtils;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class Regress78493232Dump implements Opcodes {
+
+  public static final String CLASS_NAME = "regress78493232.Test";
+  public static final String CLASS_DESC = DescriptorUtils.javaTypeToDescriptor(CLASS_NAME);
+  public static final String CLASS_INTERNAL = DescriptorUtils.descriptorToInternalName(CLASS_DESC);
+
+  public static final String UTILS_CLASS_NAME = Regress78493232Utils.class.getCanonicalName();
+  public static final String UTILS_CLASS_DESC =
+      DescriptorUtils.javaTypeToDescriptor(UTILS_CLASS_NAME);
+  public static final String UTILS_CLASS_INTERNAL =
+      DescriptorUtils.descriptorToInternalName(UTILS_CLASS_DESC);
+
+  static final int iterations = 1000;
+
+  // Arguments that have been seen when issue occurred.
+  static byte arg0 = 13;
+  static short arg1 = 25;
+  static int arg2 = 312;
+
+  // Static state of the class seen when the issue occurred.
+  static int staticIntA = 0;
+  static int staticIntB = 29;
+  static byte[] staticByteArray =
+      new byte[] {
+        63, 101, 52, -33, 9, -21, 21, 51, -59, -6, 65, -27, -37, -2, -5, 1, 33, -33, 2, 13, 4, -12,
+        -53, 54, 2, -15, 46, 2, 13, 4, -3, 30, -47, 9, 0, -13, 3, -11, -10, 13, -2, 61, -69, -6, 6,
+        -1, 15, -8, 63, -22, -33, -19, 50, -35, -3, 7, 8, 2, -7, 9, -21, 21, 51, -62, 11, -13, 7,
+        57, -37, -38, 6, -1, 15, -8, -53, 3, -19, 19, 50, -53, 3, -19, 19, 50, 9, -21, 21, 51, -59,
+        -6, 65, -27, -6, 10, -51, 21, -2, -11, -4, 11, -6, 1, 1, 11, -3, 61, -50, 50, -75, 75, -52,
+        0, 52, -53, -9, -3, -4, 14, 2, -15, 49, -41, 11, -18, 0, 39, -35, 14, -3, -1, -13, 9, -21,
+        21, 51, -59, -6, 65, -70, 7, -3, 12, -5, -9, 2, -13, 23, -27, 9, -11, 15, -6, 11, 11, 11, 7,
+        16, -31, -2, 4, 7, 9, -21, 21, 51, -69, 14, 2, -18, 3, 9, -11, -5, 75, -37, -18, 2, -18, 3,
+        13, 19, -15, -13, 10, -11, 2, -1, -7, 7, -15, 15, 5, 9, -11, 15, 13, 4, -3, -18, 3, 0, 13,
+        -9, -6, 32, -21, -4, 8, 24, -28, -3, 0, 3, -10, 9, -21, 21, 51, -59, -6, 65, -20, -55, 5,
+        15, 36, -49, 0, 17, -24, 48, -37, -2, -5, 1, 33, -33, 2, 13, 4, -12, 9, -21, 21, 51, -59,
+        -6, 65, -24, -35, -3, 7, 22, -38, 1, 4, -5, 1, 33, -33, 2, 13, 4, -12, 1, 11, -3, 61, -50,
+        50, -75, 75, -52, 0, 52, -52, 63, -77, 2, -15, 47, -51, 4, 15, -13, 4, 13, -11, 25, -33, 5,
+        -3, 17, -6, 2, 33, -37, -9, 13, 2, -17, 5, -3, -7, -3, 14, -3, 33, -41, 11, -18, 0, 2, -15,
+        43, -37, -5, -1, 19, -13, 11, -2, -13, 10, -14, 3, 6, 5, 54, -65, -4, 69, -23, -41, -8, 13,
+        -9, 3, 1, 1, 8, -9, -6, 21, -4, 20, -8, 9, -21, 21, 51, -62, 11, -13, 7, 57, -21, -41, 11,
+        -18, 0, 39, -35, 14, -3, -1, -13, -3, 14, -3, 32, -33, -19, 1, 11, -3, 62, -51, 51, -76, 76,
+        -53, 0, 53, -54, 14, -15, 33, -18, 0, 1, 9, -21, 21, 51, -59, -6, 65, -22, -29, -19, 19, 24,
+        -37, -2, -5, 1, 33, -33, 2, 13, 4, -12, 2, -15, 36, -34, 3, -1, 11, -13, -2, -5, 2, -15, 51,
+        -33, -17, 4, 3, -9, 1, 15, 21, -17, -19, 12, 9, -21, 21, 51, -59, -6, 65, -24, -35, -3, 7,
+        9, -21, 21, 51, -59, -6, 65, -24, -35, -3, 7, 33, -33, -14, 16, -15, 9, -7, -4, 5, -3, 21,
+        -3, 19, -8, 9, -19, 4, 43, -37, -6
+      };
+
+  public static byte[] dump() {
+    return dump(arg0, arg1, arg2, staticIntA, staticIntB, staticByteArray);
+  }
+
+  public static byte[] dump(
+      byte arg0, short arg1, int arg2, int staticIntA, int staticIntB, byte[] staticByteArray) {
+    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+    FieldVisitor fv;
+    MethodVisitor mv;
+
+    cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, CLASS_INTERNAL, null, "java/lang/Object", null);
+
+    {
+      fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, "staticByteArray", "[B", null, null);
+      fv.visitEnd();
+    }
+    {
+      fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, "staticIntA", "I", null, null);
+      fv.visitEnd();
+    }
+    {
+      fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, "staticIntB", "I", null, null);
+      fv.visitEnd();
+    }
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "run", "()Ljava/lang/String;", null, null);
+      mv.visitCode();
+      mv.visitIntInsn(BIPUSH, arg0);
+      mv.visitIntInsn(SIPUSH, arg1);
+      mv.visitIntInsn(SIPUSH, arg2);
+      mv.visitMethodInsn(
+          INVOKESTATIC, CLASS_INTERNAL, "methodCausingIssue", "(BSI)Ljava/lang/String;", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "getHash", "()I", null, null);
+      mv.visitCode();
+      getHash(mv);
+      mv.visitInsn(IRETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      mv.visitCode();
+
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ISTORE, 1);
+      Label l0 = new Label();
+      mv.visitLabel(l0);
+      mv.visitVarInsn(ILOAD, 1);
+      mv.visitLdcInsn(new Integer(iterations));
+      Label l1 = new Label();
+      mv.visitJumpInsn(IF_ICMPGE, l1);
+      {
+        mv.visitMethodInsn(INVOKESTATIC, CLASS_INTERNAL, "run", "()Ljava/lang/String;", false);
+
+        mv.visitVarInsn(ASTORE, 2);
+        mv.visitVarInsn(ALOAD, 2);
+        mv.visitLdcInsn("java.security.SecureRandom");
+        mv.visitMethodInsn(
+            INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
+        Label l2 = new Label();
+        mv.visitJumpInsn(IFNE, l2);
+        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        mv.visitLdcInsn("result incorrect: ");
+        mv.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        mv.visitVarInsn(ALOAD, 2);
+        mv.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        mv.visitInsn(ICONST_1);
+        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "exit", "(I)V", false);
+        mv.visitInsn(RETURN);
+
+        mv.visitLabel(l2);
+        getHash(mv);
+        mv.visitLdcInsn(new Integer(419176645));
+        Label l3 = new Label();
+        mv.visitJumpInsn(IF_ICMPEQ, l3);
+        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        mv.visitLdcInsn("state incorrect: ");
+        mv.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        printState(mv);
+        mv.visitInsn(ICONST_2);
+        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "exit", "(I)V", false);
+        mv.visitInsn(RETURN);
+        mv.visitLabel(l3);
+      }
+      mv.visitIincInsn(1, 1);
+      mv.visitJumpInsn(GOTO, l0);
+      mv.visitLabel(l1);
+
+      println(mv, "Completed successfully after " + iterations + " iterations");
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+
+    {
+      mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      mv.visitCode();
+      mv.visitIntInsn(BIPUSH, staticIntA);
+      mv.visitFieldInsn(PUTSTATIC, CLASS_INTERNAL, "staticIntA", "I");
+      mv.visitIntInsn(BIPUSH, staticIntB);
+      mv.visitFieldInsn(PUTSTATIC, CLASS_INTERNAL, "staticIntB", "I");
+      mv.visitLdcInsn(staticByteArray.length);
+      mv.visitIntInsn(NEWARRAY, T_BYTE);
+      for (int i = 0; i < staticByteArray.length; i++) {
+        mv.visitInsn(DUP);
+        mv.visitIntInsn(SIPUSH, i);
+        mv.visitIntInsn(BIPUSH, staticByteArray[i]);
+        mv.visitInsn(BASTORE);
+      }
+      mv.visitFieldInsn(PUTSTATIC, CLASS_INTERNAL, "staticByteArray", "[B");
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PRIVATE + ACC_STATIC,
+              "methodCausingIssue",
+              "(BSI)Ljava/lang/String;",
+              null,
+              null);
+      mv.visitCode();
+      Label l0 = new Label();
+      mv.visitJumpInsn(GOTO, l0);
+      mv.visitLabel(l0);
+      mv.visitIntInsn(SIPUSH, 472);
+      mv.visitVarInsn(ILOAD, 2);
+      mv.visitInsn(ISUB);
+      mv.visitVarInsn(ISTORE, 2);
+      mv.visitInsn(ICONST_0);
+      mv.visitVarInsn(ISTORE, 3);
+      mv.visitTypeInsn(NEW, "java/lang/String");
+      mv.visitInsn(DUP);
+      mv.visitIntInsn(BIPUSH, 119);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitInsn(ISUB);
+      mv.visitVarInsn(ISTORE, 0);
+      mv.visitIincInsn(1, 1);
+      mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticByteArray", "[B");
+      mv.visitVarInsn(ASTORE, 4);
+      mv.visitVarInsn(ILOAD, 1);
+      mv.visitIntInsn(NEWARRAY, T_BYTE);
+      mv.visitVarInsn(ALOAD, 4);
+      Label l1 = new Label();
+      mv.visitJumpInsn(IFNONNULL, l1);
+      Label l2 = new Label();
+      mv.visitJumpInsn(GOTO, l2);
+      mv.visitLabel(l1);
+      Label l3 = new Label();
+      mv.visitJumpInsn(GOTO, l3);
+      mv.visitLabel(l2);
+      mv.visitVarInsn(ILOAD, 1);
+      mv.visitVarInsn(ILOAD, 2);
+      Label l4 = new Label();
+      mv.visitJumpInsn(GOTO, l4);
+      Label l5 = new Label();
+      mv.visitLabel(l5);
+      mv.visitInsn(INEG);
+      mv.visitInsn(IADD);
+      mv.visitVarInsn(ISTORE, 0);
+      mv.visitJumpInsn(GOTO, l3);
+      mv.visitLabel(l3);
+      mv.visitInsn(DUP);
+      mv.visitVarInsn(ILOAD, 3);
+      mv.visitIincInsn(3, 1);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitInsn(I2B);
+      mv.visitInsn(BASTORE);
+      mv.visitVarInsn(ILOAD, 3);
+      mv.visitVarInsn(ILOAD, 1);
+      Label l6 = new Label();
+      mv.visitJumpInsn(IF_ICMPNE, l6);
+      Label l7 = new Label();
+      mv.visitJumpInsn(GOTO, l7);
+      mv.visitLabel(l6);
+      Label l8 = new Label();
+      mv.visitJumpInsn(GOTO, l8);
+      mv.visitLabel(l7);
+      mv.visitInsn(ICONST_0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/String", "<init>", "([BI)V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitLabel(l8);
+      mv.visitVarInsn(ILOAD, 0);
+      mv.visitVarInsn(ALOAD, 4);
+      mv.visitIincInsn(2, 1);
+      mv.visitVarInsn(ILOAD, 2);
+      mv.visitInsn(BALOAD);
+      Label l9 = new Label();
+      mv.visitJumpInsn(GOTO, l9);
+      mv.visitLabel(l4);
+      mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntB", "I");
+      mv.visitIntInsn(BIPUSH, 125);
+      mv.visitInsn(IADD);
+      mv.visitInsn(DUP);
+      mv.visitIntInsn(SIPUSH, 128);
+      mv.visitInsn(IREM);
+      mv.visitFieldInsn(PUTSTATIC, CLASS_INTERNAL, "staticIntA", "I");
+      mv.visitInsn(ICONST_2);
+      mv.visitInsn(IREM);
+      Label l10 = new Label();
+      mv.visitJumpInsn(IFEQ, l10);
+      Label l11 = new Label();
+      mv.visitJumpInsn(GOTO, l11);
+      mv.visitLabel(l10);
+      Label l12 = new Label();
+      mv.visitJumpInsn(GOTO, l12);
+      Label l13 = new Label();
+      mv.visitLabel(l13);
+      mv.visitInsn(INEG);
+      mv.visitInsn(IADD);
+      mv.visitVarInsn(ISTORE, 0);
+      mv.visitJumpInsn(GOTO, l3);
+      mv.visitLabel(l9);
+      mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntA", "I");
+      mv.visitIntInsn(BIPUSH, 29);
+      mv.visitInsn(IADD);
+      mv.visitInsn(DUP);
+      mv.visitIntInsn(SIPUSH, 128);
+      mv.visitInsn(IREM);
+      mv.visitFieldInsn(PUTSTATIC, CLASS_INTERNAL, "staticIntB", "I");
+      mv.visitInsn(ICONST_2);
+      mv.visitInsn(IREM);
+      Label l14 = new Label();
+      mv.visitJumpInsn(IFNE, l14);
+      Label l15 = new Label();
+      mv.visitJumpInsn(GOTO, l15);
+      mv.visitLabel(l14);
+      Label l16 = new Label();
+      mv.visitJumpInsn(GOTO, l16);
+      Label l17 = new Label();
+      mv.visitLabel(l17);
+      mv.visitInsn(INEG);
+      mv.visitInsn(IADD);
+      mv.visitVarInsn(ISTORE, 0);
+      mv.visitJumpInsn(GOTO, l3);
+      Label l18 = new Label();
+      mv.visitLabel(l18);
+      mv.visitTableSwitchInsn(0, 1, l11, new Label[] {l13, l5});
+      mv.visitLabel(l12);
+      mv.visitInsn(ICONST_1);
+      mv.visitJumpInsn(GOTO, l18);
+      mv.visitLabel(l11);
+      mv.visitInsn(ICONST_0);
+      mv.visitJumpInsn(GOTO, l18);
+      Label l19 = new Label();
+      mv.visitLabel(l19);
+      mv.visitTableSwitchInsn(0, 1, l15, new Label[] {l5, l17});
+      mv.visitLabel(l16);
+      mv.visitInsn(ICONST_0);
+      mv.visitJumpInsn(GOTO, l19);
+      mv.visitLabel(l15);
+      mv.visitInsn(ICONST_1);
+      mv.visitJumpInsn(GOTO, l19);
+      mv.visitMaxs(8, 5);
+      mv.visitEnd();
+    }
+
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  private static void printState(MethodVisitor mv) {
+    println(mv, "staticIntA:");
+    mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntA", "I");
+    printlnInt(mv);
+
+    println(mv, "staticIntB:");
+    mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntB", "I");
+    printlnInt(mv);
+
+    println(mv, "staticByteArray:");
+    mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticByteArray", "[B");
+    printByteArray(mv);
+  }
+
+  private static void getHash(MethodVisitor mv) {
+    mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntA", "I");
+    mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntB", "I");
+    mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticByteArray", "[B");
+    mv.visitMethodInsn(INVOKESTATIC, UTILS_CLASS_INTERNAL, "getHash", "(II[B)I", false);
+  }
+
+  private static void printByteArray(MethodVisitor mv) {
+    mv.visitMethodInsn(INVOKESTATIC, UTILS_CLASS_INTERNAL, "printByteArray", "([B)V", false);
+  }
+
+  private static void printlnInt(MethodVisitor mv) {
+    mv.visitMethodInsn(INVOKESTATIC, UTILS_CLASS_INTERNAL, "println", "(I)V", false);
+  }
+
+  private static void printlnString(MethodVisitor mv) {
+    mv.visitMethodInsn(
+        INVOKESTATIC, UTILS_CLASS_INTERNAL, "println", "(Ljava/lang/String;)V", false);
+  }
+
+  private static void println(MethodVisitor mv, String msg) {
+    mv.visitLdcInsn(msg);
+    printlnString(mv);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
new file mode 100644
index 0000000..14b17ce
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
@@ -0,0 +1,35 @@
+// 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.regress.b78493232;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Regress78493232Utils {
+
+  public static void println(String msg) {
+    System.out.println(msg);
+  }
+
+  public static void println(int msg) {
+    System.out.println(msg);
+  }
+
+  public static void printByteArray(byte[] array) {
+    List<String> strings = new ArrayList<>(array.length);
+    for (byte b : array) {
+      strings.add(Byte.toString(b));
+    }
+    System.out.println(String.join(",", strings));
+  }
+
+  public static int getHash(int a, int b, byte[] c) {
+    return a + 7 * b + 13 * Arrays.hashCode(c);
+  }
+
+  public static void printHash(int a, int b, byte[] c) {
+    System.out.println(getHash(a, b, c));
+  }
+}
diff --git a/third_party/r8.tar.gz.sha1 b/third_party/r8.tar.gz.sha1
new file mode 100644
index 0000000..fbb2aa8
--- /dev/null
+++ b/third_party/r8.tar.gz.sha1
@@ -0,0 +1 @@
+38b85dcea75f12c37332a5425c87733e78754ba6
\ No newline at end of file
diff --git a/third_party/shadow.tar.gz.sha1 b/third_party/shadow.tar.gz.sha1
deleted file mode 100644
index 2dfb69e..0000000
--- a/third_party/shadow.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2e350b99a72aba5d45b018b0f5391130c8751371
\ No newline at end of file
diff --git a/tools/gradle.py b/tools/gradle.py
index 862dd57..ff90779 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -16,17 +16,11 @@
 GRADLE_SHA1 = os.path.join(GRADLE_DIR, 'gradle.tar.gz.sha1')
 GRADLE_TGZ = os.path.join(GRADLE_DIR, 'gradle.tar.gz')
 
-SHADOW_DIR = os.path.join(utils.REPO_ROOT, 'third_party')
-SHADOW_SHA1 = os.path.join(SHADOW_DIR, 'shadow.tar.gz.sha1')
-SHADOW_TGZ = os.path.join(SHADOW_DIR, 'shadow.tar.gz')
-
 if utils.IsWindows():
   GRADLE = os.path.join(GRADLE_DIR, 'gradle', 'bin', 'gradle.bat')
 else:
   GRADLE = os.path.join(GRADLE_DIR, 'gradle', 'bin', 'gradle')
 
-SHADOW = os.path.join(SHADOW_DIR, 'shadow', 'shadow-2.0.1.jar')
-
 def PrintCmd(s):
   if type(s) is list:
     s = ' '.join(s)
@@ -44,19 +38,8 @@
   else:
     print 'gradle.py: Gradle binary present'
 
-def EnsureShadow():
-  if not os.path.exists(SHADOW) or os.path.getmtime(SHADOW_TGZ) < os.path.getmtime(SHADOW_SHA1):
-    # Bootstrap or update gradle, everything else is controlled using gradle.
-    utils.DownloadFromGoogleCloudStorage(SHADOW_SHA1)
-    # Update the mtime of the tar file to make sure we do not run again unless
-    # there is an update.
-    os.utime(SHADOW_TGZ, None)
-  else:
-    print 'gradle.py: Shadow library present'
-
 def EnsureDeps():
   EnsureGradle()
-  EnsureShadow()
 
 def RunGradleIn(gradleCmd, args, cwd, throw_on_failure=True, env=None):
   EnsureDeps()