Merge "Disable check-cast removal"
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index b84b343..0c9fbe8 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.FeatureClassMapping;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
@@ -27,7 +28,8 @@
 
 public class DexSplitterHelper {
 
-  public static void run(D8Command command, String featureSplitMapping, String outputArchive)
+  public static void run(
+      D8Command command, String featureSplitMapping, String outputArchive, String proguardMap)
       throws IOException, CompilationException, ExecutionException {
     InternalOptions options = command.getInternalOptions();
     options.enableDesugaring = false;
@@ -45,7 +47,12 @@
             new ApplicationReader(command.getInputApp(), options, timing).read(null, executor);
         FeatureClassMapping featureClassMapping =
             new FeatureClassMapping(Paths.get(featureSplitMapping));
-        Map<String, Builder> applications = getDistribution(app, featureClassMapping);
+
+        ClassNameMapper mapper = null;
+        if (proguardMap != null) {
+          mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
+        }
+        Map<String, Builder> applications = getDistribution(app, featureClassMapping, mapper);
         for (Entry<String, Builder> entry : applications.entrySet()) {
           DexApplication featureApp = entry.getValue().build();
           // We use the same factory, reset sorting.
@@ -88,10 +95,13 @@
   }
 
   private static Map<String, Builder> getDistribution(
-      DexApplication app, FeatureClassMapping featureClassMapping) throws FeatureMappingException {
+      DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
+      throws FeatureMappingException {
     Map<String, Builder> applications = new HashMap<>();
     for (DexProgramClass clazz : app.classes()) {
-      String feature = featureClassMapping.featureForClass(clazz.toString());
+      String clazzName =
+          mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
+      String feature = featureClassMapping.featureForClass(clazzName);
       Builder featureApplication = applications.get(feature);
       if (featureApplication == null) {
         featureApplication = DexApplication.builder(app.dexItemFactory, app.timing);
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index a9e4a18..8e184c0 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -27,6 +27,7 @@
     List<String> inputArchives = new ArrayList<>();
     String splitBaseName = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
     String featureSplitMapping;
+    String proguardMap;
   }
 
   private static Options parseArguments(String[] args) throws IOException {
@@ -43,6 +44,11 @@
         options.splitBaseName = output;
         continue;
       }
+      String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
+      if (proguardMap != null) {
+        options.proguardMap = proguardMap;
+        continue;
+      }
       String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
       if (featureSplit != null) {
         options.featureSplitMapping = featureSplit;
@@ -70,7 +76,8 @@
     // We set the actual consumer on the ApplicationWriter when we have calculated the distribution
     // since we don't yet know the distribution.
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    DexSplitterHelper.run(builder.build(), options.featureSplitMapping, options.splitBaseName);
+    DexSplitterHelper.run(
+        builder.build(), options.featureSplitMapping, options.splitBaseName, options.proguardMap);
   }
 
   public static void main(String[] args) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9cd430a..cdb419f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -196,7 +197,15 @@
                 newArrayEmpty.size(), newType);
             iterator.replaceCurrentInstruction(newNewArray);
           }
-        }
+        } else if (current.isNewInstance()) {
+            NewInstance newInstance= current.asNewInstance();
+            DexType newClazz = graphLense.lookupType(newInstance.clazz, method);
+            if (newClazz != newInstance.clazz) {
+              NewInstance newNewInstance =
+                  new NewInstance(newClazz, makeOutValue(newInstance, code));
+              iterator.replaceCurrentInstruction(newNewInstance);
+            }
+          }
       }
     }
     assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7af0451..24def66 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -73,6 +73,7 @@
 
   public static final int REGISTER_CANDIDATE_NOT_FOUND = -1;
   public static final int MIN_CONSTANT_FREE_FOR_POSITIONS = 5;
+  private static final int RESERVED_MOVE_EXCEPTION_REGISTER = 0;
 
   private enum ArgumentReuseMode {
     ALLOW_ARGUMENT_REUSE,
@@ -141,6 +142,9 @@
   // List of intervals that no register has been allocated to sorted by first live range.
   protected PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
 
+  // List of intervals for the result of move-exception instructions.
+  private List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
+
   // The first register used for parallel moves. After register allocation the parallel move
   // temporary registers are [firstParallelMoveTemporary, maxRegisterNumber].
   private int firstParallelMoveTemporary = NO_REGISTER;
@@ -153,6 +157,11 @@
   // register.
   private boolean hasDedicatedMoveExceptionRegister = false;
 
+  private int getMoveExceptionRegister() {
+    return numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS +
+        RESERVED_MOVE_EXCEPTION_REGISTER;
+  }
+
   public LinearScanRegisterAllocator(IRCode code, InternalOptions options) {
     this.code = code;
     this.options = options;
@@ -663,6 +672,7 @@
     active.clear();
     inactive.clear();
     unhandled.clear();
+    moveExceptionIntervals.clear();
     for (LiveIntervals intervals : liveIntervals) {
       intervals.clearRegisterAssignment();
     }
@@ -752,7 +762,6 @@
       // Force all move exception ranges to start out with the exception in a fixed register. Split
       // their live ranges which will force another register if used.
       int moveExceptionRegister = NO_REGISTER;
-      List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
       boolean overlappingMoveExceptionIntervals = false;
       for (BasicBlock block : code.blocks) {
         for (Instruction instruction : block.getInstructions()) {
@@ -761,8 +770,12 @@
             Value exceptionValue = instruction.outValue();
             LiveIntervals intervals = exceptionValue.getLiveIntervals();
             unhandled.remove(intervals);
+            moveExceptionIntervals.add(intervals);
             if (moveExceptionRegister == NO_REGISTER) {
+              assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
               moveExceptionRegister = getFreeConsecutiveRegisters(1);
+              assert moveExceptionRegister == getMoveExceptionRegister();
+              assert !freeRegisters.contains(moveExceptionRegister);
             }
             intervals.setRegister(moveExceptionRegister);
             if (!overlappingMoveExceptionIntervals) {
@@ -770,7 +783,6 @@
                 overlappingMoveExceptionIntervals |= other.overlaps(intervals);
               }
             }
-            moveExceptionIntervals.add(intervals);
           }
         }
       }
@@ -966,8 +978,14 @@
       }
       current = current.getNextConsecutive();
     }
-    // Select registers.
     current = unhandledInterval.getStartOfConsecutive();
+    // Exclude move exception register if the first interval overlaps a move exception interval.
+    if (overlapsMoveExceptionInterval(current) &&
+        freeRegisters.remove(getMoveExceptionRegister())) {
+      assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
+      excludedRegisters.add(getMoveExceptionRegister());
+    }
+    // Select registers.
     int numberOfRegister = current.numberOfConsecutiveRegisters();
     int firstRegister = getFreeConsecutiveRegisters(numberOfRegister);
     for (int i = 0; i < numberOfRegister; i++) {
@@ -1124,6 +1142,54 @@
     return false;
   }
 
+  // Intervals overlap a move exception interval if one of the splits of the intervals does.
+  // Since spill and restore moves are always put after the move exception we cannot give
+  // a non-move exception interval the same register as a move exception instruction.
+  //
+  // For example:
+  //
+  // B0:
+  //   const v0, 0
+  //   invoke throwing_method v0 (catch handler B2)
+  //   goto B1
+  // B1:
+  //   ...
+  // B2:
+  //   move-exception v1
+  //   invoke method v0
+  //   return
+  //
+  // During register allocation we could split the const number intervals into multiple
+  // parts. We have to avoid assigning the same register to v1 and and v0 in B0 even
+  // if v0 has a different register in B2. That is because the spill/restore move when
+  // transitioning from B0 to B2 has to be after the move-exception instruction.
+  //
+  // Assuming that v0 has register 0 in B0 and register 4 in B2 and v1 has register 0 in B2
+  // we would generate the following incorrect code:
+  //
+  // B0:
+  //   const r0, 0
+  //   invoke throwing_method r0 (catch handler B2)
+  //   goto B1
+  // B1:
+  //   ...
+  // B2:
+  //   move-exception r0
+  //   move r4, r0  // Whoops.
+  //   invoke method r4
+  //   return
+  private boolean overlapsMoveExceptionInterval(LiveIntervals intervals) {
+    if (!hasDedicatedMoveExceptionRegister) {
+      return false;
+    }
+    for (LiveIntervals moveExceptionInterval : moveExceptionIntervals) {
+      if (intervals.anySplitOverlaps(moveExceptionInterval)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private boolean allocateSingleInterval(LiveIntervals unhandledInterval, ArgumentReuseMode mode) {
     int registerConstraint = unhandledInterval.getRegisterLimit();
     assert registerConstraint <= Constants.U16BIT_MAX;
@@ -1182,8 +1248,8 @@
     // register. If we cannot find a free valid register for the move exception value we have no
     // place to put a spill move (because the move exception instruction has to be the
     // first instruction in the handler block).
-    if (hasDedicatedMoveExceptionRegister) {
-      int moveExceptionRegister = numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS;
+    if (overlapsMoveExceptionInterval(unhandledInterval)) {
+      int moveExceptionRegister = getMoveExceptionRegister();
       if (moveExceptionRegister <= registerConstraint) {
         freePositions.set(moveExceptionRegister, 0);
       }
@@ -1530,8 +1596,8 @@
     }
 
     // Disallow reuse of the move exception register if we have reserved one.
-    if (hasDedicatedMoveExceptionRegister) {
-      usePositions.set(numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS, 0);
+    if (overlapsMoveExceptionInterval(unhandledInterval)) {
+      usePositions.set(getMoveExceptionRegister(), 0);
     }
 
     // Treat active linked argument intervals as pinned. They cannot be given another register
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index b6d8b79..2bbdaf9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -325,6 +325,19 @@
     return nextOverlap(other) != -1;
   }
 
+  public boolean anySplitOverlaps(LiveIntervals other) {
+    LiveIntervals parent = getSplitParent();
+    if (parent.overlaps(other)) {
+      return true;
+    }
+    for (LiveIntervals child : parent.getSplitChildren()) {
+      if (child.overlaps(other)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public int nextOverlap(LiveIntervals other) {
     Iterator<LiveRange> it = other.ranges.iterator();
     LiveRange otherRange = it.next();
diff --git a/src/test/examples/naming001/keep-rules-106.txt b/src/test/examples/naming001/keep-rules-106.txt
new file mode 100644
index 0000000..ca320eb
--- /dev/null
+++ b/src/test/examples/naming001/keep-rules-106.txt
@@ -0,0 +1,5 @@
+# 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.
+
+-applymapping mapping-106.txt
\ No newline at end of file
diff --git a/src/test/examples/naming001/mapping-106.txt b/src/test/examples/naming001/mapping-106.txt
new file mode 100644
index 0000000..1b3606d
--- /dev/null
+++ b/src/test/examples/naming001/mapping-106.txt
@@ -0,0 +1 @@
+naming001.E -> a.a:
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 562abaf..c451402 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.io.ByteStreams;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -105,11 +104,6 @@
     ensureSameOutput(main, mergedApp, classes);
   }
 
-  protected byte[] asBytes(Class clazz) throws IOException {
-    return ByteStreams
-        .toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
-  }
-
   protected AndroidApp buildAndroidApp(byte[]... classes) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
     for (byte[] clazz : classes) {
@@ -184,10 +178,6 @@
     }
   }
 
-  protected static byte[] getBytesFromJavaClass(Class clazz) throws IOException {
-    return Files.readAllBytes(ToolHelper.getClassFileForTestClass(clazz));
-  }
-
   private static class DumpLoader extends ClassLoader {
 
     @SuppressWarnings("deprecation")
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 11bc8cc..83cc931 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -440,6 +440,10 @@
   private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler";
   private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
 
+  public static byte[] getClassAsBytes(Class clazz) throws IOException {
+    return ByteStreams.toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
+  }
+
   public static String getArtDir(DexVm version) {
     String dir = ART_DIRS.get(version);
     if (dir == null) {
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java
new file mode 100644
index 0000000..76fcd19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.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.debug;
+
+public class BreakInOneLineFinallyTest {
+  int i = 0;
+
+  void foo() {
+    try { bar(); }
+    finally { baz(); } // Java will fail to break here on exceptional exit.
+  }
+
+  int bar() {
+    if (i++ % 2 == 0) {
+      System.out.println("bar return " + i);
+      return i;
+    }
+    System.out.println("bar throw " + i);
+    throw new RuntimeException("" + i);
+  }
+
+  void baz() {
+    System.out.println("baz");
+  }
+
+  public static void main(String[] args) {
+    BreakInOneLineFinallyTest test = new BreakInOneLineFinallyTest();
+    test.foo();
+    try {
+      test.foo();
+    } catch (RuntimeException e) {
+      System.out.println("Caught expected exception: " + e.getMessage());
+      return;
+    }
+    throw new RuntimeException("Test failed...");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
new file mode 100644
index 0000000..6d7d1a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
@@ -0,0 +1,52 @@
+// 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.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInOneLineFinallyTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = BreakInOneLineFinallyTest.class;
+  private static final String FILE = CLASS.getSimpleName() + ".java";
+  private static final String NAME = CLASS.getCanonicalName();
+
+  private final DebugTestConfig config;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    DelayedDebugTestConfig cf =
+            temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+    DelayedDebugTestConfig d8 =
+            temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+    return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+  }
+
+  public BreakInOneLineFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+    this.config = config.getConfig(temp);
+  }
+
+  @Test
+  public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+    Assume.assumeFalse(
+        "b/72933440 : JavaC doesn't duplicate line-table entries when duplicating finally blocks",
+        config instanceof D8DebugTestConfig);
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "foo", 11),
+        run(),
+        checkLine(FILE, 11), // hit finally on normal flow
+        breakpoint(NAME, "main", 34), // can't hit the exceptional block :-(
+        run(),
+        checkLine(FILE, 34),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
new file mode 100644
index 0000000..eec9d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
@@ -0,0 +1,40 @@
+// 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.debug;
+
+public class BreakInTwoLinesFinallyTest {
+  int i = 0;
+
+  void foo() {
+    try { bar(); }
+    finally {
+      baz();
+    }
+  }
+
+  int bar() {
+    if (i++ % 2 == 0) {
+      System.out.println("bar return " + i);
+      return i;
+    }
+    System.out.println("bar throw " + i);
+    throw new RuntimeException("" + i);
+  }
+
+  void baz() {
+    System.out.println("baz");
+  }
+
+  public static void main(String[] args) {
+    BreakInTwoLinesFinallyTest test = new BreakInTwoLinesFinallyTest();
+    test.foo();
+    try {
+      test.foo();
+    } catch (RuntimeException e) {
+      System.out.println("Caught expected exception: " + e.getMessage());
+      return;
+    }
+    throw new RuntimeException("Test failed...");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
new file mode 100644
index 0000000..7691a2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
@@ -0,0 +1,53 @@
+// 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.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInTwoLinesFinallyTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = BreakInTwoLinesFinallyTest.class;
+  private static final String FILE = CLASS.getSimpleName() + ".java";
+  private static final String NAME = CLASS.getCanonicalName();
+
+  private final DebugTestConfig config;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    DelayedDebugTestConfig cf =
+            temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+    DelayedDebugTestConfig d8 =
+            temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+    return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+  }
+
+  public BreakInTwoLinesFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+    this.config = config.getConfig(temp);
+  }
+
+  @Test
+  public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1_HOST));
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "foo", 12),
+        run(),
+        checkLine(FILE, 12), // hit finally on normal flow
+        run(),
+        checkLine(FILE, 12), // hit finally on exceptional flow
+        breakpoint(NAME, "main", 36),
+        run(),
+        checkLine(FILE, 36),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
index d535f72..8480d24 100644
--- a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
@@ -35,6 +35,14 @@
     }
   }
 
+  public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, Class... classes) {
+    return compileAndAddClasses(temp, Arrays.asList(classes));
+  }
+
+  public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, List<Class> classes) {
+    return compileAndAdd(temp, ListUtils.map(classes, ToolHelper::getClassFileForTestClass), null);
+  }
+
   public D8DebugTestConfig compileAndAdd(TemporaryFolder temp, Path... paths) {
     return compileAndAdd(temp, Arrays.asList(paths), null);
   }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index aff3708..556e17f 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -37,12 +37,14 @@
     return paths;
   }
 
-  public void addPaths(Path... paths) {
+  public DebugTestConfig addPaths(Path... paths) {
     addPaths(Arrays.asList(paths));
+    return this;
   }
 
-  public void addPaths(List<Path> paths) {
+  public DebugTestConfig addPaths(List<Path> paths) {
     this.paths.addAll(paths);
+    return this;
   }
 
   /** Proguard map that the debuggee has been translated according to, null if not present. */
@@ -50,8 +52,9 @@
     return proguardMap;
   }
 
-  public void setProguardMap(Path proguardMap) {
+  public DebugTestConfig setProguardMap(Path proguardMap) {
     this.proguardMap = proguardMap;
+    return this;
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
new file mode 100644
index 0000000..9abc732
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
@@ -0,0 +1,22 @@
+// 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.debug;
+
+public class LocalChangeOnSameLineTest {
+  int i = 0;
+
+  int bar() {
+    System.out.println("bar call " + ++i);
+    return i;
+  }
+
+  void foo() {
+    { int x = bar(); int y = bar(); }
+    { int x = bar(); int y = bar(); }
+  }
+
+  public static void main(String[] args) {
+    new LocalChangeOnSameLineTest().foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
new file mode 100644
index 0000000..b588590
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
@@ -0,0 +1,73 @@
+// 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.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LocalChangeOnSameLineTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = LocalChangeOnSameLineTest.class;
+  private static final String FILE = CLASS.getSimpleName() + ".java";
+  private static final String NAME = CLASS.getCanonicalName();
+
+  private final DebugTestConfig config;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    DelayedDebugTestConfig cf =
+            temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+    DelayedDebugTestConfig d8 =
+            temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+    return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+  }
+
+  public LocalChangeOnSameLineTestRunner(String name, DelayedDebugTestConfig config) {
+    this.config = config.getConfig(temp);
+  }
+
+  /** Test that only hit the break point at line 15 once. */
+  @Test
+  public void testHitBreakpointOnce() throws Throwable {
+    Assume.assumeFalse("b/72933440 : invalid line info table", config instanceof D8DebugTestConfig);
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "foo", 15),
+        run(),
+        checkLine(FILE, 15),
+        breakpoint(NAME, "main", 21),
+        run(),
+        checkLine(FILE, 21),
+        run());
+  }
+
+  /** Test that locals are correct in the frame of foo each time we break in bar. */
+  @Test
+  public void testLocalsOnBreakpoint() throws Throwable {
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "bar"),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkNoLocal("x")),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkLocal("x")),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkNoLocal("x")),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkLocal("x")),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index c803867..2e8938a 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -6,17 +6,27 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -39,7 +49,7 @@
    * therefore can't run without the base being loaded.
    */
   @Test
-  public void splitFiles() throws CompilationFailedException, IOException {
+  public void splitFilesNoObfuscation() throws CompilationFailedException, IOException {
     // Initial normal compile to create dex files.
     Path inputDex = temp.newFolder().toPath().resolve("input.zip");
     D8.run(
@@ -52,13 +62,7 @@
             .build());
 
     Path outputDex = temp.getRoot().toPath().resolve("output");
-    Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
-    try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
-      out.write(
-          "dexsplitsample.Class1:base\n"
-              + "dexsplitsample.Class2:feature1\n"
-              + "dexsplitsample.Class3:feature1");
-    }
+    Path splitSpec = createSplitSpec();
 
     DexSplitter.main(
         new String[] {
@@ -104,4 +108,83 @@
       // We expect this to throw since base is not in the path and Class3 depends on Class1
     }
   }
+
+  private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
+    Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
+    try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
+      out.write(
+          "dexsplitsample.Class1:base\n"
+              + "dexsplitsample.Class2:feature1\n"
+              + "dexsplitsample.Class3:feature1");
+    }
+    return splitSpec;
+  }
+
+  private List<String> getProguardConf() {
+    return ImmutableList.of(
+        "-keep class dexsplitsample.Class3 {",
+        "  public static void main(java.lang.String[]);",
+        "}");
+  }
+
+  @Test
+  public void splitFilesObfuscation()
+      throws CompilationFailedException, IOException, ExecutionException {
+    // Initial normal compile to create dex files.
+    Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+    Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
+
+    R8.run(
+        R8Command.builder()
+            .setOutput(inputDex, OutputMode.DexIndexed)
+            .addProgramFiles(Paths.get(CLASS1_CLASS))
+            .addProgramFiles(Paths.get(CLASS2_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+            .setProguardMapOutputPath(proguardMap)
+            .addProguardConfiguration(getProguardConf(), null)
+            .build());
+
+    Path outputDex = temp.getRoot().toPath().resolve("output");
+    Path splitSpec = createSplitSpec();
+
+    DexSplitter.main(
+        new String[] {
+          "--input", inputDex.toString(),
+          "--output", outputDex.toString(),
+          "--feature-splits", splitSpec.toString(),
+          "--proguard-map", proguardMap.toString()
+        });
+
+    Path base = outputDex.getParent().resolve("output.base.zip");
+    Path feature = outputDex.getParent().resolve("output.feature1.zip");
+    String class3 = "dexsplitsample.Class3";
+    // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
+    // class1 which is in base.
+    ArtCommandBuilder builder = new ArtCommandBuilder();
+    builder.appendClasspath(base.toString());
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass(class3);
+    String out = ToolHelper.runArt(builder);
+    assertEquals(out, "Class3\n");
+
+    // Class1 should not be in the feature, it should still be in base.
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass(class3);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class3 depends on Class1.
+    }
+
+    // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
+    // shaken away.
+    DexInspector inspector = new DexInspector(base, proguardMap.toString());
+    ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
+    assertTrue(subject.isPresent());
+    assertTrue(subject.isRenamed());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 7b1cf3e..5bf5a36 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
@@ -26,24 +27,24 @@
   @IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
   public void testInvokeSuperTargets() throws Exception {
     ensureSameOutput(MainClass.class.getCanonicalName(),
-        asBytes(MainClass.class),
-        asBytes(Consumer.class),
-        asBytes(Super.class),
-        asBytes(SubLevel1.class),
-        asBytes(SubLevel2.class),
+        ToolHelper.getClassAsBytes(MainClass.class),
+        ToolHelper.getClassAsBytes(Consumer.class),
+        ToolHelper.getClassAsBytes(Super.class),
+        ToolHelper.getClassAsBytes(SubLevel1.class),
+        ToolHelper.getClassAsBytes(SubLevel2.class),
         InvokerClassDump.dump(),
-        asBytes(SubclassOfInvokerClass.class));
+        ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
   }
 
   @Test
   public void testInvokeSuperTargetsNonVerifying() throws Exception {
     ensureR8FailsWithCompilationError(MainClassFailing.class.getCanonicalName(),
-        asBytes(MainClassFailing.class),
-        asBytes(Consumer.class),
-        asBytes(Super.class),
-        asBytes(SubLevel1.class),
-        asBytes(SubLevel2.class),
+        ToolHelper.getClassAsBytes(MainClassFailing.class),
+        ToolHelper.getClassAsBytes(Consumer.class),
+        ToolHelper.getClassAsBytes(Super.class),
+        ToolHelper.getClassAsBytes(SubLevel1.class),
+        ToolHelper.getClassAsBytes(SubLevel2.class),
         InvokerClassFailingDump.dump(),
-        asBytes(SubclassOfInvokerClass.class));
+        ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 39cf369..6242b93 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
@@ -47,7 +48,7 @@
       boolean npeCaught,
       BiConsumer<AppInfo, TypeAnalysis> inspector)
       throws Exception {
-    AndroidApp app = buildAndroidApp(asBytes(mainClass));
+    AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
     DexApplication dexApplication =
         new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
             .read().toDirect();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
index 496ada2..b6347d1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
@@ -36,7 +37,7 @@
       int expectedNumberOfNonNull,
       Consumer<IRCode> testAugmentedIRCode)
       throws Exception {
-    AndroidApp app = buildAndroidApp(asBytes(testClass));
+    AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
     DexApplication dexApplication =
         new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
             .read().toDirect();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index c72cdcb..1226876 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.code.Format21t;
 import com.android.tools.r8.code.Format22t;
 import com.android.tools.r8.code.Instruction;
@@ -27,7 +28,7 @@
   }
 
   private void buildAndTest(Class<?> testClass, List<MethodSignature> signatures) throws Exception {
-    AndroidApp app = buildAndroidApp(asBytes(testClass));
+    AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
     AndroidApp r8Result = compileWithR8(
         app, keepMainProguardConfiguration(testClass), o -> o.inlineAccessors = false);
     DexInspector dexInspector = new DexInspector(r8Result);
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 1631a52..276b763 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -219,13 +221,40 @@
         main.iterateInstructions(InstructionSubject::isInvoke);
     // mapping-105 simply includes: naming001.D#keep -> peek
     // naming001.E extends D, hence its keep() should be renamed to peek as well.
-    // Skip E#<init>
-    iterator.next();
+    // Check E#<init> is not renamed.
+    InvokeInstructionSubject init = iterator.next();
+    assertEquals("Lnaming001/E;-><init>()V", init.invokedMethod().toSmaliString());
     // E#keep() should be replaced with peek by applying the map.
     InvokeInstructionSubject m = iterator.next();
     assertEquals("peek", m.invokedMethod().name.toSourceString());
-    // E could be renamed randomly, though.
-    assertNotEquals("naming001.E", m.holder().toString());
+    // D must not be renamed
+    assertEquals("naming001.D", m.holder().toString());
+  }
+
+  @Test
+  public void test_naming001_rule106() throws Exception {
+    // keep rules just to rename E
+    Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-106.txt");
+    Path proguardMap = out.resolve(MAPPING);
+    AndroidApp outputApp =
+        runR8(
+            ToolHelper.addProguardConfigurationConsumer(
+                getCommandForApps(out, flag, NAMING001_JAR).setDisableMinification(true),
+                pgConfig -> {
+                  pgConfig.setPrintMapping(true);
+                  pgConfig.setPrintMappingFile(proguardMap);
+                })
+                .build());
+
+    // Make sure the given proguard map is indeed applied.
+    DexInspector inspector = new DexInspector(outputApp);
+    MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+
+    // naming001.E is renamed to a.a, so first instruction must be: new-instance La/a;
+    Instruction[] instructions = main.getMethod().getCode().asDexCode().instructions;
+    assertTrue(instructions[0] instanceof NewInstance);
+    NewInstance newInstance = (NewInstance) instructions[0];
+    assertEquals( "La/a;", newInstance.getType().toSmaliString());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index 3725a24..0584aa0 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -46,7 +46,7 @@
     List<byte[]> allBytes = new ArrayList<>();
     allBytes.addAll(ASM_CLASSES);
     for (Class clazz : CLASSES) {
-      allBytes.add(getBytesFromJavaClass(clazz));
+      allBytes.add(ToolHelper.getClassAsBytes(clazz));
     }
     ensureSameOutput(Main.class.getCanonicalName(),
         ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()),