Add a new debug test with a bad local variable scope

- Add support into test framework allowing to set breakpoint
on a specific line.

Bug: 69093793
Change-Id: Iddc672d6613dde305d2337f1e4fc6dd359fdfbbb
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 71f2a66..e32f2ca 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -353,6 +353,18 @@
     return c;
   }
 
+  public static void localVisibilityIntoLoop(double B[][], double A[][]) {
+    int i;
+    int length = 1;
+    for (i = 0; i < length; i++) {
+      double Bi[] = B[i];
+      double Ai[] = A[i];
+      for (int j = 0; j < 1; j += 4) {
+        Bi[j] = Ai[j];
+      }
+    }
+  }
+
   public static void main(String[] args) {
     noLocals();
     unusedLocals();
@@ -377,5 +389,6 @@
     System.out.println(localConstantBis(true));
     System.out.println(localTriggeringCSE());
     System.out.println(intAddition(1, 2, 6));
+    localVisibilityIntoLoop(new double[1][1], new double[1][1]);
   }
 }
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 740d4e3..8db3bf8 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
 import java.io.File;
@@ -121,6 +122,8 @@
 
   private static final DexCompilerKind DEX_COMPILER_KIND = DexCompilerKind.D8;
 
+  private static final int FIRST_LINE = -1;
+
   // Set to true to enable verbose logs
   private static final boolean DEBUG_TESTS = false;
 
@@ -410,9 +413,18 @@
     return breakpoint(className, methodName, null);
   }
 
+  protected final JUnit3Wrapper.Command breakpoint(String className, String methodName, int line) {
+    return breakpoint(className, methodName, null, line);
+  }
+
   protected final JUnit3Wrapper.Command breakpoint(String className, String methodName,
       String methodSignature) {
-    return new JUnit3Wrapper.Command.BreakpointCommand(className, methodName, methodSignature);
+    return breakpoint(className, methodName, methodSignature, FIRST_LINE);
+  }
+
+  protected final JUnit3Wrapper.Command breakpoint(String className, String methodName,
+      String methodSignature, int line) {
+    return new JUnit3Wrapper.Command.BreakpointCommand(className, methodName, methodSignature, line);
   }
 
   protected final JUnit3Wrapper.Command stepOver() {
@@ -1425,18 +1437,30 @@
       setState(State.WaitForEvent);
     }
 
-    private long getMethodFirstCodeIndex(long classId, long breakpointMethodId) {
+    private LongList getMethodCodeIndex(long classId, long breakpointMethodId, int lineToSearch) {
+      LongList pcs = new LongArrayList();
       ReplyPacket replyPacket = getMirror().getLineTable(classId, breakpointMethodId);
       checkReplyPacket(replyPacket, "Failed to get method line table");
-      replyPacket.getNextValueAsLong(); // start
-      replyPacket.getNextValueAsLong(); // end
+      long start = replyPacket.getNextValueAsLong(); // start
+      long end = replyPacket.getNextValueAsLong(); // end
       int linesCount = replyPacket.getNextValueAsInt();
       if (linesCount == 0) {
-        return -1;
+        pcs.add(-1L);
       } else {
-        // Read only the 1st line because code indices are in ascending order
-        return replyPacket.getNextValueAsLong();
+        if (lineToSearch == FIRST_LINE) {
+          // Read only the 1st line because code indices are in ascending order
+          pcs.add(replyPacket.getNextValueAsLong());
+        } else {
+          for (int entry = 0; entry < linesCount; entry++) {
+            long pc = replyPacket.getNextValueAsLong();
+            long lineNumber = replyPacket.getNextValueAsInt();
+            if (lineNumber == lineToSearch) {
+              pcs.add(pc);
+            }
+          }
+        }
       }
+      return pcs;
     }
 
     //
@@ -1465,14 +1489,16 @@
         private final String methodName;
         private final String methodSignature;
         private boolean requestedClassPrepare = false;
+        private int line;
 
         public BreakpointCommand(String className, String methodName,
-            String methodSignature) {
+            String methodSignature, int line) {
           assert className != null;
           assert methodName != null;
           this.className = className;
           this.methodName = methodName;
           this.methodSignature = methodSignature;
+          this.line = line;
         }
 
         @Override
@@ -1513,13 +1539,15 @@
                 testBase.translator.getObfuscatedMethodName(className, methodName, methodSignature);
             long breakpointMethodId =
                 findMethod(mirror, classId, obfuscatedMethodName, methodSignature);
-            long index = testBase.getMethodFirstCodeIndex(classId, breakpointMethodId);
-            Assert.assertTrue("No code in method", index >= 0);
-            ReplyPacket replyPacket = testBase.getMirror().setBreakpoint(
-                new Location(typeTag, classId, breakpointMethodId, index), SuspendPolicy.ALL);
-            assert replyPacket.getErrorCode() == Error.NONE;
-            int breakpointId = replyPacket.getNextValueAsInt();
-            testBase.events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
+            LongList pcs = testBase.getMethodCodeIndex(classId, breakpointMethodId, line);
+            for (long pc : pcs) {
+              Assert.assertTrue("No code in method", pc >= 0);
+              ReplyPacket replyPacket = testBase.getMirror().setBreakpoint(
+                  new Location(typeTag, classId, breakpointMethodId, pc), SuspendPolicy.ALL);
+              assert replyPacket.getErrorCode() == Error.NONE;
+              int breakpointId = replyPacket.getNextValueAsInt();
+              testBase.events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
+            }
           }
         }
 
@@ -1575,6 +1603,10 @@
           sb.append(className);
           sb.append(" method=");
           sb.append(methodName);
+          sb.append(" signature=");
+          sb.append(methodSignature);
+          sb.append(" line=");
+          sb.append(line);
           return sb.toString();
         }
       }
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 1ef7f3e..b2c551b 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.FrameInspector;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.Tag;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -16,6 +20,8 @@
  */
 public class LocalsTest extends DebugTestBase {
 
+  private static final boolean RUN_JAVA = false;
+
   public static final String SOURCE_FILE = "Locals.java";
 
   @Test
@@ -680,4 +686,47 @@
         checkLocal("c", Value.createInt(11)),
         run());
   }
+
+  @Test
+  @Ignore("b/69093793")
+  public void testLocalVisibilityIntoLoop() throws Throwable {
+    final String className = "Locals";
+    final String methodName = "localVisibilityIntoLoop";
+
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint(className, methodName, 359));
+    commands.add(run());
+    commands.add(checkMethod(className, methodName));
+    commands.add(checkLine(SOURCE_FILE, 359));
+    commands.add(checkNoLocal("Ai"));
+    commands.add(checkNoLocal("Bi"));
+    commands.add(checkNoLocal("i"));
+    commands.add(stepOver());
+    commands.add(checkMethod(className, methodName));
+    commands.add(checkLine(SOURCE_FILE, 360));
+    commands.add(checkNoLocal("Ai"));
+    commands.add(checkNoLocal("Bi"));
+    commands.add(checkLocal("i", Value.createInt(0)));
+    commands.add(stepOver());
+    commands.add(checkMethod(className, methodName));
+    commands.add(checkLine(SOURCE_FILE, 361));
+    commands.add(checkNoLocal("Ai"));
+    commands.add(checkLocal("Bi"));
+    commands.add(checkLocal("i", Value.createInt(0)));
+    commands.add(stepOver());
+    commands.add(checkMethod(className, methodName));
+    commands.add(checkLine(SOURCE_FILE, 362));
+    commands.add(checkLocal("Ai"));
+    commands.add(checkLocal("Bi"));
+    commands.add(checkLocal("i", Value.createInt(0)));
+    commands.add(run());
+    commands.add(checkMethod(className, methodName));
+    commands.add(checkLine(SOURCE_FILE, 359));
+    commands.add(checkNoLocal("Ai"));
+    commands.add(checkNoLocal("Bi"));
+    commands.add(checkLocal("i", Value.createInt(0)));
+    commands.add(run());
+
+    runDebugTest(getDebuggeeDexD8OrCf(RUN_JAVA), className, commands);
+  }
 }