Merge "Test for stepping into a method with local changes prior to the first line."
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index efec188..5c6abba 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -736,7 +736,7 @@
     }
   }
 
-  private String extractClassName(byte[] ccc) {
+  public String extractClassName(byte[] ccc) {
     class ClassNameExtractor extends ClassVisitor {
       private String className;
 
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 b6cdb48..445dd53 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
 import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
@@ -508,6 +509,15 @@
     return t -> inspector.accept(t.debuggeeState);
   }
 
+  protected final JUnit3Wrapper.Command conditional(
+      Function<JUnit3Wrapper.DebuggeeState, List<JUnit3Wrapper.Command>> conditional) {
+    return t -> subcommands(conditional.apply(t.debuggeeState)).perform(t);
+  }
+
+  protected final JUnit3Wrapper.Command subcommands(List<JUnit3Wrapper.Command> commands) {
+    return t -> Lists.reverse(commands).forEach(t.commandsQueue::addFirst);
+  }
+
   protected final JUnit3Wrapper.Command setLocal(String localName, Value newValue) {
     return new JUnit3Wrapper.Command.SetLocalCommand(localName, newValue);
   }
diff --git a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest.java b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest.java
new file mode 100644
index 0000000..027d709
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import java.util.Collection;
+import java.util.List;
+
+public class StepIntoMethodWithTypeParameterArgumentsTest {
+
+  public static List<Object> field = null;
+
+  public static void foo(List<String> strings) {
+    Collection<Object> objects = field;
+  }
+
+  public static void main(String[] args) {
+    foo(null);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestDump.java b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestDump.java
new file mode 100644
index 0000000..ef1cab1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestDump.java
@@ -0,0 +1,153 @@
+// 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 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 StepIntoMethodWithTypeParameterArgumentsTestDump implements Opcodes {
+
+  public static byte[] dump(boolean moveFirstLineEntry) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_SUPER,
+        "com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest",
+        null,
+        "java/lang/Object",
+        null);
+
+    classWriter.visitSource("StepIntoMethodWithTypeParameterArgumentsTest.java", null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_PUBLIC | ACC_STATIC,
+              "field",
+              "Ljava/util/List;",
+              "Ljava/util/List<Ljava/lang/Object;>;",
+              null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(9, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable(
+          "this",
+          "Lcom/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest;",
+          null,
+          label0,
+          label1,
+          0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "foo",
+              "(Ljava/util/List;)V",
+              "(Ljava/util/List<Ljava/lang/String;>;)V",
+              null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      if (!moveFirstLineEntry) {
+        // Removed to have 'strings' start at undefined line number.
+        methodVisitor.visitLineNumber(14, label0);
+      }
+      methodVisitor.visitFieldInsn(
+          GETSTATIC,
+          "com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest",
+          "field",
+          "Ljava/util/List;");
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      if (moveFirstLineEntry) {
+        // Move the line entry to after 'strings' has already become live.
+        Label labelNop = new Label();
+        methodVisitor.visitLabel(labelNop);
+        methodVisitor.visitLineNumber(14, labelNop);
+        methodVisitor.visitInsn(NOP);
+      }
+      // Now at line 13 objects will be live.
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(15, label1);
+      methodVisitor.visitInsn(RETURN);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLocalVariable(
+          "strings", "Ljava/util/List;", "Ljava/util/List<Ljava/lang/String;>;", label0, label2, 0);
+      methodVisitor.visitLocalVariable(
+          "objects",
+          "Ljava/util/Collection;",
+          "Ljava/util/Collection<Ljava/lang/Object;>;",
+          label1,
+          label2,
+          1);
+      methodVisitor.visitMaxs(1, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitInsn(ACONST_NULL);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest",
+          "foo",
+          "(Ljava/util/List;)V",
+          false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(19, label1);
+      methodVisitor.visitInsn(RETURN);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(11, label0);
+      methodVisitor.visitInsn(ACONST_NULL);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC,
+          "com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTest",
+          "field",
+          "Ljava/util/List;");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.java
new file mode 100644
index 0000000..aa4542a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/StepIntoMethodWithTypeParameterArgumentsTestRunner.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 static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Test;
+
+/** Tests debugging behavior with regards to exception handling */
+public class StepIntoMethodWithTypeParameterArgumentsTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = StepIntoMethodWithTypeParameterArgumentsTest.class;
+  private static final String NAME = CLASS.getCanonicalName();
+  private static final String DESC = DescriptorUtils.javaTypeToDescriptor(NAME);
+  private static final String FILE = CLASS.getSimpleName() + ".java";
+
+  @Test
+  public void testCf() throws Throwable {
+    byte[] bytes = StepIntoMethodWithTypeParameterArgumentsTestDump.dump(true);
+    assertEquals(NAME, extractClassName(bytes));
+    // Java jumps to first instruction of the catch handler, matching the source code.
+    Path jar = temp.getRoot().toPath().resolve("test.jar");
+    ArchiveConsumer archiveConsumer = new ArchiveConsumer(jar);
+    archiveConsumer.accept(ByteDataView.of(bytes), DESC, null);
+    archiveConsumer.finished(null);
+    run(new CfDebugTestConfig().addPaths(jar));
+  }
+
+  @Test
+  public void testD8() throws Throwable {
+    Path out = temp.getRoot().toPath().resolve("out.jar");
+    D8.run(
+        D8Command.builder()
+            .addClassProgramData(
+                StepIntoMethodWithTypeParameterArgumentsTestDump.dump(true), Origin.unknown())
+            .setOutput(out, OutputMode.DexIndexed)
+            .build());
+    run(new DexDebugTestConfig().addPaths(out));
+  }
+
+  private void run(DebugTestConfig config) throws Throwable {
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "main"),
+        run(),
+        checkLine(FILE, 18), // First line in main.
+        stepInto(),
+        checkLine(FILE, -1), // First line in foo is undefined due to ASM dump change.
+        checkLocal("strings"),
+        checkNoLocal("objects"),
+        stepOver(),
+        // Step will skip line 14 and hit 15 on JVM but will (correctly?) hit 14 on Art.
+        subcommands(
+            config instanceof CfDebugTestConfig
+                ? ImmutableList.of()
+                : ImmutableList.of(checkLine(FILE, 14), stepOver())),
+        checkLine(FILE, 15),
+        checkLocal("strings"),
+        checkLocal("objects"),
+        run());
+  }
+}