Version 1.6.61.

Cherry pick: Allow the local variable table to contain unobservable info.
CL: https://r8-review.googlesource.com/c/r8/+/47780

Cherry pick: Make sure to have an IR instruction materialized for local ends.
CL: https://r8-review.googlesource.com/c/r8/+/47789

BUG=147865212
R=zerny@google.com

Change-Id: I7d8e0a2ad4ba8456641d4d6650bf5eb9e41f198e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 467fc64..034b2f8 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.6.60";
+  public static final String LABEL = "1.6.61";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 75f6be9..39eb34e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -30,7 +30,7 @@
 
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
-    // Intentionally left empty.
+    builder.addNop();
   }
 
   @Override
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 995a583..883fb9f 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
@@ -974,6 +974,12 @@
         if (currentBlock.getInstructions().isEmpty()) {
           addInstruction(new DebugLocalRead());
         } else {
+          // We do not want to add the out value of an instructions as a debug value for
+          // the same instruction. Debug values are there to keep values alive until that
+          // instruction. Therefore, they make no sense on the instruction that defines
+          // the value. There should always be a DebugLocalRead in the IR for situations
+          // where an introduced local's scope ends immediately.
+          assert !debugLocalEnds.contains(currentBlock.getInstructions().getLast().outValue());
           attachLocalValues(currentBlock.getInstructions().getLast());
         }
       }
@@ -1204,6 +1210,11 @@
         addInstruction(new DebugLocalWrite(out, in));
         return;
       }
+      // If this move ends locals, add a DebugLocalRead to make sure the end point
+      // is registered in the right place.
+      if (!debugLocalEnds.isEmpty()) {
+        addInstruction(new DebugLocalRead());
+      }
     }
     currentBlock.writeCurrentDefinition(dest, in, ThrowingInfo.NO_THROW);
   }
@@ -1227,6 +1238,15 @@
     addInstruction(instruction);
   }
 
+  public void addNop() {
+    // If locals end on a nop, insert a debug local read as the ending point.
+    // This avoids situations where the end of the local could be the instruction
+    // that introduced it when the local only spans a nop in the input.
+    if (!debugLocalEnds.isEmpty()) {
+      addInstruction(new DebugLocalRead());
+    }
+  }
+
   public void addRem(NumericType type, int dest, int left, int right) {
     boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
     Value in1 = readNumericRegister(left, type);
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
new file mode 100644
index 0000000..aa2ac6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Flaf2Dump.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2020, 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.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file with the following code, line number table,
+// and local variable table.
+//
+//  public static final java.lang.String box(int);
+//    descriptor: (I)Ljava/lang/String;
+//    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+//    Code:
+//      stack=1, locals=3, args_size=1
+//         0: nop
+//         1: ldc           #9                  // String OK
+//         3: astore_1
+//         4: nop
+//         5: iload_0
+//         6: istore_2
+//         7: nop
+//         8: nop
+//         9: iload_0
+//        10: istore_2
+//        11: nop
+//        12: aload_1
+//        13: areturn
+//        14: astore_1
+//        15: nop
+//        16: iload_0
+//        17: istore_2
+//        18: nop
+//        19: nop
+//        20: iload_0
+//        21: istore_2
+//        22: nop
+//        23: aload_1
+//        24: athrow
+//      Exception table:
+//         from    to  target type
+//             0     4    14   any
+//            14    15    14   any
+//      StackMapTable: number_of_entries = 1
+//        frame_type = 78 /* same_locals_1_stack_item */
+//          stack = [ class java/lang/Throwable ]
+//      LineNumberTable:
+//        line 2: 0
+//        line 3: 1
+//        line 5: 4
+//        line 6: 5
+//        line 8: 8
+//        line 9: 9
+//        line 10: 11
+//        line 3: 13
+//        line 12: 14
+//        line 5: 15
+//        line 6: 16
+//        line 8: 19
+//        line 9: 20
+//        line 10: 22
+//      LocalVariableTable:
+//        Start  Length  Slot  Name   Signature
+//            7       1     2     z   I
+//           11       1     2     z   I
+//           18       1     2     z   I
+//           22       1     2     z   I
+//            0      25     0     x   I
+public class Flaf2Dump implements Opcodes {
+
+  public static byte[] dump() throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(
+        V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Flaf.kt", null);
+
+    {
+      annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+      annotationVisitor0.visit("mv", new int[] {1, 1, 17});
+      annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+      annotationVisitor0.visit("k", new Integer(2));
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+        annotationVisitor1.visit(
+            null,
+            "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+        annotationVisitor1.visitEnd();
+      }
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+        annotationVisitor1.visit(null, "box");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visitEnd();
+      }
+      annotationVisitor0.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+      {
+        annotationVisitor0 =
+            methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+        annotationVisitor0.visitEnd();
+      }
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      Label label2 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+      Label label3 = new Label();
+      methodVisitor.visitTryCatchBlock(label2, label3, label2, null);
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(2, label0);
+      methodVisitor.visitInsn(NOP);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(3, label4);
+      methodVisitor.visitLdcInsn("OK");
+      methodVisitor.visitVarInsn(ASTORE, 0);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(5, label1);
+      methodVisitor.visitInsn(NOP);
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(6, label5);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitInsn(NOP);
+      Label label7 = new Label();
+      methodVisitor.visitLabel(label7);
+      methodVisitor.visitLineNumber(8, label7);
+      methodVisitor.visitInsn(NOP);
+      Label label8 = new Label();
+      methodVisitor.visitLabel(label8);
+      methodVisitor.visitLineNumber(9, label8);
+      methodVisitor.visitInsn(ICONST_4);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label9 = new Label();
+      methodVisitor.visitLabel(label9);
+      methodVisitor.visitInsn(NOP);
+      Label label10 = new Label();
+      methodVisitor.visitLabel(label10);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      Label label11 = new Label();
+      methodVisitor.visitLabel(label11);
+      methodVisitor.visitLineNumber(3, label11);
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(11, label2);
+      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+      methodVisitor.visitVarInsn(ASTORE, 0);
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(5, label3);
+      methodVisitor.visitInsn(NOP);
+      Label label12 = new Label();
+      methodVisitor.visitLabel(label12);
+      methodVisitor.visitLineNumber(6, label12);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label13 = new Label();
+      methodVisitor.visitLabel(label13);
+      methodVisitor.visitInsn(NOP);
+      Label label14 = new Label();
+      methodVisitor.visitLabel(label14);
+      methodVisitor.visitLineNumber(8, label14);
+      methodVisitor.visitInsn(NOP);
+      Label label15 = new Label();
+      methodVisitor.visitLabel(label15);
+      methodVisitor.visitLineNumber(9, label15);
+      methodVisitor.visitInsn(ICONST_4);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label16 = new Label();
+      methodVisitor.visitLabel(label16);
+      methodVisitor.visitInsn(NOP);
+      Label label17 = new Label();
+      methodVisitor.visitLabel(label17);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitInsn(ATHROW);
+      methodVisitor.visitLocalVariable("z", "I", null, label6, label7, 1);
+      methodVisitor.visitLocalVariable("z", "I", null, label9, label10, 1);
+      methodVisitor.visitLocalVariable("z", "I", null, label13, label14, 1);
+      methodVisitor.visitLocalVariable("z", "I", null, label16, label17, 1);
+      methodVisitor.visitMaxs(1, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
new file mode 100644
index 0000000..d0a4f6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/FlafDump.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, 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.b147865212;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generate a class file containing the following method, line number table,
+// and local variable table. Conditionally add an extra entry to the line
+// number table for the nop at instruction 5. When generateLineNumberForLocal
+// is true, a `line 7: 5` entry is generated in the line number table
+// making the local observable in the debugger.
+//
+//  public static final java.lang.String box();
+//    descriptor: ()Ljava/lang/String;
+//    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+//    Code:
+//      stack=1, locals=1, args_size=0
+//         0: nop
+//         1: ldc           #11                 // String A
+//         3: areturn
+//         4: astore_0
+//         5: nop
+//         6: ldc           #13                 // String B
+//         8: areturn
+//      Exception table:
+//         from    to  target type
+//             0     4     4   Class java/lang/IllegalStateException
+//      StackMapTable: number_of_entries = 1
+//        frame_type = 68 /* same_locals_1_stack_item */
+//          stack = [ class java/lang/IllegalStateException ]
+//      LineNumberTable:
+//        line 2: 0
+//        line 3: 1
+//        line 4: 4
+//        line 5: 6
+//        line 6: 6
+//      LocalVariableTable:
+//        Start  Length  Slot  Name   Signature
+//            5       1     0     e   Ljava/lang/IllegalStateException;
+public class FlafDump implements Opcodes {
+  public static byte[] dump(boolean generateLineNumberForLocal) throws Exception {
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(
+        V1_6, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, "FlafKt", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Flaf.kt", null);
+
+    {
+      annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+      annotationVisitor0.visit("mv", new int[] {1, 1, 16});
+      annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+      annotationVisitor0.visit("k", new Integer(2));
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+        annotationVisitor1.visit(
+            null,
+            "\u0000\u0006\n\u0000\n\u0002\u0010\u000e\u001a\u0006\u0010\u0000\u001a\u00020\u0001");
+        annotationVisitor1.visitEnd();
+      }
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+        annotationVisitor1.visit(null, "box");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visitEnd();
+      }
+      annotationVisitor0.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "box", "()Ljava/lang/String;", null, null);
+      {
+        annotationVisitor0 =
+            methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false);
+        annotationVisitor0.visitEnd();
+      }
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      Label label1 = new Label();
+      methodVisitor.visitTryCatchBlock(label0, label1, label1, "java/lang/IllegalStateException");
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(2, label0);
+      methodVisitor.visitInsn(NOP);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(3, label2);
+      methodVisitor.visitLdcInsn("A");
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(4, label1);
+      methodVisitor.visitFrame(
+          Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IllegalStateException"});
+      methodVisitor.visitVarInsn(ASTORE, 0);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      if (generateLineNumberForLocal) {
+        methodVisitor.visitLineNumber(7, label3);
+      }
+      methodVisitor.visitInsn(NOP);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(5, label4);
+      methodVisitor.visitLineNumber(6, label4);
+      methodVisitor.visitLdcInsn("B");
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitLocalVariable(
+          "e", "Ljava/lang/IllegalStateException;", null, label3, label4, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
new file mode 100644
index 0000000..d7133e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b147865212/Regress147865212.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, 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.b147865212;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class Regress147865212 extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public Regress147865212(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean hasLocal(MethodSubject method) {
+    return Arrays.stream(method.getMethod().getCode().asDexCode().getDebugInfo().events)
+        .anyMatch(event -> event instanceof StartLocal);
+  }
+
+  private MethodSubject build(byte[] classFile) throws Exception {
+    CodeInspector inspector =
+        testForD8()
+            .addProgramClassFileData(classFile)
+            .setMinApi(parameters.getApiLevel())
+            .debug()
+            .compile()
+            .inspector();
+    ClassSubject clazz = inspector.clazz("FlafKt");
+    assertTrue(clazz.isPresent());
+    MethodSubject method = clazz.uniqueMethodWithName("box");
+    assertTrue(method.isPresent());
+    return method;
+  }
+
+  @Test
+  public void testFlafWithLineNumberForLocal() throws Exception {
+    // Generate the FlafKt class file adding a line number for the single nop instruction
+    // that a local spans. That will make the local observable in the debugger as there
+    // is now a breakpoint on the nop. Therefore, the local stays in the output.
+    MethodSubject method = build(FlafDump.dump(true));
+    assertTrue(hasLocal(method));
+  }
+
+  @Test
+  public void testFlafWithoutLineNumberForLocal() throws Exception {
+    // Generate the FlafKt class file where all locals are live only on a nop instruction
+    // with no line number. Therefore, the locals are not observable in the debugger and
+    // are removed.
+    MethodSubject method = build(FlafDump.dump(false));
+    assertFalse(hasLocal(method));
+  }
+
+  @Test
+  public void testFlaf2() throws Exception {
+    MethodSubject method = build(Flaf2Dump.dump());
+    assertFalse(hasLocal(method));
+  }
+}