Merge "Replace ZipInputstream by ZipFile"
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 52fff08..1cc21a3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -124,6 +124,12 @@
     debugValues.clear();
   }
 
+  public void moveDebugValue(Value value, Instruction target) {
+    assert debugValues.contains(value);
+    value.replaceDebugUser(this, target);
+    debugValues.remove(value);
+  }
+
   /**
    * Returns the basic block containing this instruction.
    */
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 5eeda96..32159c1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -79,6 +79,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -1789,6 +1790,26 @@
         assert returnType == MoveType.fromDexType(method.method.proto.returnType);
       }
       closeCurrentBlock();
+
+      // Collect the debug values which are live on all returns.
+      Set<Value> debugValuesForReturn = Sets.newIdentityHashSet();
+      for (Value value : exitBlocks.get(0).exit().getDebugValues()) {
+        boolean include = true;
+        for (int i = 1; i < exitBlocks.size() && include; i++) {
+          include = exitBlocks.get(i).exit().getDebugValues().contains(value);
+        }
+        if (include) {
+          debugValuesForReturn.add(value);
+        }
+      }
+
+      // Move all these debug values to the new return.
+      for (Value value : debugValuesForReturn) {
+        for (BasicBlock block : exitBlocks) {
+          block.exit().moveDebugValue(value, normalExitBlock.exit());
+        }
+      }
+
       // Replace each return instruction with a goto to the new exit block.
       List<Value> operands = new ArrayList<>();
       for (BasicBlock block : exitBlocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index ea1ef57..24e0396 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -569,7 +569,6 @@
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
     registerAllocator.allocateRegisters(options.debug);
     printMethod(code, "After register allocation (non-SSA)");
-    printLiveRanges(registerAllocator, "Final live ranges.");
     if (!options.debug) {
       CodeRewriter.removedUnneededDebugPositions(code);
     }
@@ -604,10 +603,4 @@
       printer.end("cfg");
     }
   }
-
-  private void printLiveRanges(LinearScanRegisterAllocator allocator, String title) {
-    if (printer != null) {
-      allocator.print(printer, title);
-    }
-  }
 }
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 f4c51ce..9b3eb83 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
@@ -2216,25 +2216,6 @@
     return true;
   }
 
-  public void print(CfgPrinter printer, String title) {
-    printer.begin("intervals");
-    printer.print("name \"").append(title).append("\"").ln();
-    PriorityQueue<LiveIntervals> sortedIntervals =
-        new PriorityQueue<>((o1, o2) -> Integer.compare(o1.getStart(), o2.getStart()));
-    sortedIntervals.addAll(liveIntervals);
-    for (LiveIntervals interval = sortedIntervals.poll();
-        interval != null;
-        interval = sortedIntervals.poll()) {
-      Value value = interval.getValue();
-      if (interval.getRanges().get(0).isInfinite()) {
-        // Skip argument sentinels.
-        continue;
-      }
-      interval.print(printer, value.getNumber(), value.getNumber());
-    }
-    printer.end("intervals");
-  }
-
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder("Live ranges:\n");
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index d42dd61..890cb85 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -244,6 +244,24 @@
     return sum + x + y;
   }
 
+  public static int argumentLiveAtReturn(int x) {
+    switch (x) {
+      case 0:
+        return 0;
+      case 1:
+        return 0;
+      case 2:
+        return 0;
+      case 100:
+        return 1;
+      case 101:
+        return 1;
+      case 102:
+        return 1;
+    }
+    return -1;
+  }
+
   public static void main(String[] args) {
     noLocals();
     unusedLocals();
@@ -259,5 +277,6 @@
     stepNonEmptyForLoopBody(3);
     tempInCase(42);
     localSwap(1, 2);
+    argumentLiveAtReturn(-1);
   }
 }
diff --git a/src/test/debugTestResourcesKotlin/KotlinApp.kt b/src/test/debugTestResourcesKotlin/KotlinApp.kt
index 7fa2be1..094407b 100644
--- a/src/test/debugTestResourcesKotlin/KotlinApp.kt
+++ b/src/test/debugTestResourcesKotlin/KotlinApp.kt
@@ -3,42 +3,27 @@
 // BSD-style license that can be found in the LICENSE file.
 
 class KotlinApp {
+
+    fun ifElse(cond: Boolean) {
+        val a = 10
+        if (cond) {
+            val b = a * 2
+            printInt(b)
+        } else {
+            val c = a / 2
+            print(c)
+        }
+    }
+
+    fun printInt(i: Int) {
+        println(i)
+    }
+
     companion object {
         @JvmStatic fun main(args: Array<String>) {
-            println("Hello world!")
             val instance = KotlinApp()
-            instance.processObject(instance, instance::printObject)
-            instance.invokeInlinedFunctions()
+            instance.ifElse(true)
+            instance.ifElse(false)
         }
     }
-
-    fun processObject(obj: Any, func: (Any) -> Unit) {
-        func(obj)
-    }
-
-    fun printObject(obj: Any) {
-        println(obj)
-    }
-
-    fun invokeInlinedFunctions() {
-        inlinedA {
-            val inA = 1
-            inlinedB {
-                val inB = 2
-                foo(inA, inB)
-            }
-        }
-    }
-
-    inline fun inlinedA(f: () -> Unit) {
-        f()
-    }
-
-    inline fun inlinedB(f: () -> Unit) {
-        f()
-    }
-
-    fun foo(a: Int, b: Int) {
-        println("a=$a, b=$b")
-    }
 }
\ No newline at end of file
diff --git a/src/test/debugTestResourcesKotlin/KotlinInline.kt b/src/test/debugTestResourcesKotlin/KotlinInline.kt
new file mode 100644
index 0000000..7f914e4
--- /dev/null
+++ b/src/test/debugTestResourcesKotlin/KotlinInline.kt
@@ -0,0 +1,59 @@
+// Copyright (c) 2017, 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.
+
+class KotlinInline {
+
+    fun processObject(obj: Any, func: (Any) -> Unit) {
+        func(obj)
+    }
+
+    fun printObject(obj: Any) {
+        println(obj)
+    }
+
+    fun invokeInlinedFunctions() {
+        inlinedA {
+            val inA = 1
+            inlinedB {
+                val inB = 2
+                foo(inA, inB)
+            }
+        }
+    }
+
+    inline fun inlinedA(f: () -> Unit) {
+        f()
+    }
+
+    inline fun inlinedB(f: () -> Unit) {
+        f()
+    }
+
+    fun foo(a: Int, b: Int) {
+        println("a=$a, b=$b")
+    }
+
+    fun emptyMethod(unused: Int) {
+    }
+
+    fun singleInline() {
+        emptyMethod(0)
+        inlined()
+        emptyMethod(1)
+    }
+
+    inline fun inlined() {
+        emptyMethod(-1)
+    }
+
+    companion object {
+        @JvmStatic fun main(args: Array<String>) {
+            println("Hello world!")
+            val instance = KotlinInline()
+            instance.processObject(instance, instance::printObject)
+            instance.invokeInlinedFunctions()
+            instance.singleInline()
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 6340728..b2b77bc 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -973,7 +973,7 @@
             .filter(m -> methodSignature.equals(m.methodSignature)).collect(
                 Collectors.toList());
       }
-      Assert.assertFalse("No method found", methodInfos.isEmpty());
+      Assert.assertFalse("No method named " + methodName + " found", methodInfos.isEmpty());
       // There must be only one matching method
       Assert.assertEquals("More than 1 method found: please specify a signature", 1,
           methodInfos.size());
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
new file mode 100644
index 0000000..e62a17b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class KotlinInlineTest extends DebugTestBase {
+
+  @Ignore("Requires kotlin-specific stepping behavior")
+  @Test
+  public void testStepOverInline() throws Throwable {
+    String methodName = "singleInline";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", methodName),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(41, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        // TODO(shertz) stepping over must take kotlin inline range into account.
+        stepOver(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(42, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepOver(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(43, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        run());
+  }
+
+  @Ignore("Requires kotlin-specific stepping behavior")
+  @Test
+  public void testStepIntoInline() throws Throwable {
+    String methodName = "singleInline";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", methodName),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(41, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        // TODO(shertz) stepping over must take kotlin inline range into account.
+        stepInto(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          // The actual line number (the one encoded in debug information) is different than the
+          // source file one.
+          // TODO(shertz) extract original line number from JSR-45's SMAP (only supported on
+          // Android O+).
+          assertTrue(42 != s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        run());
+  }
+
+  @Ignore("Requires kotlin-specific stepping behavior")
+  @Test
+  public void testStepOutInline() throws Throwable {
+    String methodName = "singleInline";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", methodName),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(41, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        // TODO(shertz) stepping out must take kotlin inline range into account.
+        stepInto(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+        }),
+        stepOut(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(43, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        run());
+  }
+
+  @Test
+  public void testKotlinInline() throws Throwable {
+    final String inliningMethodName = "invokeInlinedFunctions";
+    runDebugTestKotlin("KotlinInline",
+        breakpoint("KotlinInline", inliningMethodName),
+        run(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(16, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          // We must have stepped into the code of the inlined method but the actual current method
+          // did not change.
+          assertEquals(inliningMethodName, s.getMethodName());
+          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(17, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(18, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("inA", Value.createInt(1));
+          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
+          s.checkLocal("$i$f$inlinedA");
+          s.checkLocal("$i$a$1$inlinedA");
+        }),
+        stepInto(),
+        inspect(s -> {
+          // We must have stepped into the code of the second inlined method but the actual current
+          // method did not change.
+          assertEquals(inliningMethodName, s.getMethodName());
+          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(19, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        stepInto(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(20, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("inB", Value.createInt(2));
+          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
+          s.checkLocal("$i$f$inlinedB");
+          s.checkLocal("$i$a$1$inlinedB");
+        }),
+        run());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinTest.java b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
index 8caa3c1..a6b57b8 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
@@ -3,97 +3,160 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import static org.junit.Assert.assertEquals;
+
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
-import org.junit.Assert;
 import org.junit.Test;
 
 public class KotlinTest extends DebugTestBase {
 
+  // TODO(shertz) simplify test
+  // TODO(shertz) add more variables ?
   @Test
-  public void testKotlinApp() throws Throwable {
-    final String inliningMethodName = "invokeInlinedFunctions";
+  public void testStepOver() throws Throwable {
     runDebugTestKotlin("KotlinApp",
         breakpoint("KotlinApp$Companion", "main"),
         run(),
         inspect(s -> {
-          Assert.assertEquals("KotlinApp.kt", s.getSourceFile());
-          Assert.assertEquals(8, s.getLineNumber());
+          assertEquals("KotlinApp$Companion", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(24, s.getLineNumber());
           s.checkLocal("this");
           s.checkLocal("args");
+          checkNoLocal("instance");
         }),
         stepOver(),
         inspect(s -> {
-          Assert.assertEquals(9, s.getLineNumber());
-          s.checkLocal("this");
-          s.checkLocal("args");
-        }),
-        stepOver(),
-        inspect(s -> {
-          Assert.assertEquals(10, s.getLineNumber());
+          assertEquals(25, s.getLineNumber());
           s.checkLocal("this");
           s.checkLocal("args");
           s.checkLocal("instance");
         }),
         stepOver(),
         inspect(s -> {
-          Assert.assertEquals(11, s.getLineNumber());
+          assertEquals(26, s.getLineNumber());
           s.checkLocal("this");
           s.checkLocal("args");
           s.checkLocal("instance");
         }),
+        run());
+  }
+
+  @Test
+  public void testStepIntoAndOut() throws Throwable {
+    runDebugTestKotlin("KotlinApp",
+        breakpoint("KotlinApp$Companion", "main"),
+        run(),
+        inspect(s -> {
+          assertEquals("KotlinApp$Companion", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(24, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          checkNoLocal("instance");
+        }),
+        stepOver(),
+        inspect(s -> {
+          assertEquals(25, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          s.checkLocal("instance");
+        }),
+        // Step into 1st invoke of ifElse
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(24, s.getLineNumber());
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(8, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(true));
+          checkNoLocal("a");
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          // We must have stepped into the code of the inlined method but the actual current method
-          // did not change.
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(9, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(true));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(25, s.getLineNumber());
+          // We should be into the 'then' statement.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(10, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(true));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(26, s.getLineNumber());
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(11, s.getLineNumber());
           s.checkLocal("this");
-          s.checkLocal("inA", Value.createInt(1));
-          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedA");
-          s.checkLocal("$i$a$1$inlinedA");
+          s.checkLocal("cond", Value.createBoolean(true));
+          s.checkLocal("a", Value.createInt(10));
+          s.checkLocal("b", Value.createInt(20));
+          checkNoLocal("c");
+        }),
+        // Go back to the main method
+        stepOut(),
+        inspect(s -> {
+          assertEquals("KotlinApp$Companion", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(26, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          checkNoLocal("instance");
+        }),
+        // Step into 2nd invoke of ifElse
+        stepInto(),
+        inspect(s -> {
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals("KotlinApp.kt", s.getSourceFile());
+          assertEquals(8, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(false));
+          checkNoLocal("a");
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          // We must have stepped into the code of the second inlined method but the actual current
-          // method did not change.
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(9, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(false));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(27, s.getLineNumber());
+          // We should be into the 'else' statement this time.
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(13, s.getLineNumber());
           s.checkLocal("this");
+          s.checkLocal("cond", Value.createBoolean(false));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          checkNoLocal("c");
         }),
         stepInto(),
         inspect(s -> {
-          Assert.assertEquals(inliningMethodName, s.getMethodName());
-          Assert.assertEquals(28, s.getLineNumber());
+          assertEquals("KotlinApp", s.getClassName());
+          assertEquals(14, s.getLineNumber());
           s.checkLocal("this");
-          s.checkLocal("inB", Value.createInt(2));
-          // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedB");
-          s.checkLocal("$i$a$1$inlinedB");
+          s.checkLocal("cond", Value.createBoolean(false));
+          s.checkLocal("a", Value.createInt(10));
+          checkNoLocal("b");
+          s.checkLocal("c", Value.createInt(5));
         }),
         run());
   }
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 02fb694..6f73f02c 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -470,4 +470,18 @@
         checkNoLocal("t"),
         run());
   }
+
+  @Test
+  public void argumentLiveAtReturn() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "argumentLiveAtReturn"),
+        run(),
+        checkLine(SOURCE_FILE, 248),
+        stepOver(),
+        checkLine(SOURCE_FILE, 262),
+        checkLocal("x", Value.createInt(-1)),
+        checkNoLocal("t"),
+        run());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index ad6f925..31872ed 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.List;
@@ -102,7 +103,8 @@
     DexDebugEntry previousEntry = null;
     for (DexDebugEntry entry : entries) {
       if (previousEntry != null) {
-        assertTrue("More than one entry defined for PC " + entry.address,
+        assertTrue(
+            "More than one entry defined for PC " + StringUtils.hexString(entry.address, 2),
             entry.address > previousEntry.address);
       }
       previousEntry = entry;
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 2782c91..5ffd815 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -336,4 +336,105 @@
     info.checkLineHasExactLocals(8, "param", "int", "x", "int");
     info.checkLineHasExactLocals(9, "param", "int");
   }
+
+  @Test
+  public void argumentLiveAtReturn() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    /*
+     This is the original Java source code.
+
+     public static int argumentLiveAtReturn(int x) {  // Line 1
+       switch (x) {
+         case 0:
+           return 0;
+         case 1:
+           return 0;
+         case 2:
+           return 0;
+         case 100:
+           return 1;
+         case 101:
+           return 1;
+         case 102:
+           return 1;
+       }
+       return -1;
+     }
+   */
+    MethodSignature foo = clazz.addStaticMethod("argumentLiveAtReturn", ImmutableList.of("I"), "I",
+        ".limit stack 2",
+        ".limit locals 1",
+        ".var 0 is x I from L0 to L8",
+        "L0:",
+        ".line 2",
+        "  iload 0",
+        "lookupswitch",
+        "  0: L1",
+        "  1: L2",
+        "  2: L3",
+        "  100: L4",
+        "  101: L5",
+        "  102: L6",
+        "  default: L7",
+        "L1:",
+        ".line 4",
+        "  iconst_0",
+        "  ireturn",
+        "L2:",
+        ".line 6",
+        "  iconst_0",
+        "  ireturn",
+        "L3:",
+        ".line 8",
+        "  iconst_0",
+        "  ireturn",
+        "L4:",
+        ".line 10",
+        "  iconst_1",
+        "  ireturn",
+        "L5:",
+        ".line 12",
+        "  iconst_1",
+        "  ireturn",
+        "L6:",
+        ".line 14",
+        "  iconst_1",
+        "  ireturn",
+        "L7:",
+        ".line 16",
+        "  iconst_m1",
+        "  ireturn",
+        "L8:"
+    );
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc -1",
+        "  invokestatic Test/argumentLiveAtReturn(I)I",
+        "  invokevirtual java/io/PrintStream/print(I)V",
+        "  return");
+
+    String expected = "-1";
+    String javaResult = runOnJava(builder, clazz.name);
+    assertEquals(expected, javaResult);
+
+    AndroidApp jasminApp = builder.build();
+    AndroidApp d8App = ToolHelper.runD8(jasminApp);
+    String artResult = runOnArt(d8App, clazz.name);
+    assertEquals(expected, artResult);
+    DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+    info.checkStartLine(2);
+    info.checkLineHasExactLocals(2, "x", "int");
+    info.checkLineHasExactLocals(4, "x", "int");
+    info.checkLineHasExactLocals(6, "x", "int");
+    info.checkLineHasExactLocals(8, "x", "int");
+    info.checkLineHasExactLocals(10, "x", "int");
+    info.checkLineHasExactLocals(12, "x", "int");
+    info.checkLineHasExactLocals(14, "x", "int");
+    info.checkLineHasExactLocals(16, "x", "int");
+  }
 }
diff --git a/tools/test_framework.py b/tools/test_framework.py
index 9e5f63f..d511bcf 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -57,16 +57,22 @@
              ' peak resident set size (VmHWM) in bytes.',
       default = False,
       action = 'store_true')
+  parser.add_argument('--output',
+                      help = 'Output directory to keep the generated files')
   return parser.parse_args()
 
 def Main():
   utils.check_java_version()
   args = parse_arguments()
+  output_dir = args.output
 
   with utils.TempDir() as temp_dir:
 
+    if not output_dir:
+      output_dir = temp_dir
+
     if args.tool in ['dx', 'goyt', 'goyt-release']:
-      tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
+      tool_args = ['--dex', '--output=' + output_dir, '--multi-dex',
           '--min-sdk-version=' + MIN_SDK_VERSION]
 
     xmx = None
@@ -80,7 +86,7 @@
       xmx = '-Xmx1600m'
     else:
       tool_file = D8_JAR
-      tool_args = ['--output', temp_dir, '--min-api', MIN_SDK_VERSION]
+      tool_args = ['--output', output_dir, '--min-api', MIN_SDK_VERSION]
       if args.tool == 'd8-release':
         tool_args.append('--release')
       xmx = '-Xmx600m'
@@ -89,7 +95,7 @@
 
     track_memory_file = None
     if args.print_memoryuse:
-      track_memory_file = os.path.join(temp_dir, utils.MEMORY_USE_TMP_FILE)
+      track_memory_file = os.path.join(output_dir, utils.MEMORY_USE_TMP_FILE)
       cmd.extend(['tools/track_memory.sh', track_memory_file])
 
     if tool_file.endswith('.jar'):
@@ -108,7 +114,7 @@
       print('{}-Total(MemoryUse): {}'
           .format(args.name, utils.grep_memoryuse(track_memory_file)))
 
-    dex_files = [f for f in glob(os.path.join(temp_dir, '*.dex'))]
+    dex_files = [f for f in glob(os.path.join(output_dir, '*.dex'))]
     code_size = 0
     for dex_file in dex_files:
       code_size += os.path.getsize(dex_file)