Merge "Support skipping of class loader frames in debug tests"
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 35760ef..de8c673 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.DescriptorUtils;
 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.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
 import java.io.File;
@@ -434,14 +435,39 @@
 
   private JUnit3Wrapper.Command step(StepKind stepKind, StepLevel stepLevel,
       StepFilter stepFilter) {
-    return new JUnit3Wrapper.Command.StepCommand(stepKind.jdwpValue, stepLevel.jdwpValue,
-        stepFilter, state -> true);
+    return new JUnit3Wrapper.Command.StepCommand(stepKind, stepLevel, stepFilter);
   }
 
   protected JUnit3Wrapper.Command stepUntil(StepKind stepKind, StepLevel stepLevel,
       Function<JUnit3Wrapper.DebuggeeState, Boolean> stepUntil) {
-    return new JUnit3Wrapper.Command.StepCommand(stepKind.jdwpValue, stepLevel.jdwpValue, NO_FILTER,
-        stepUntil);
+    return stepUntil(stepKind, stepLevel, stepUntil, DEFAULT_FILTER);
+  }
+
+  protected JUnit3Wrapper.Command stepUntil(StepKind stepKind, StepLevel stepLevel,
+      Function<JUnit3Wrapper.DebuggeeState, Boolean> stepUntil, StepFilter stepFilter) {
+    // We create an extension to the given step filter which will also check whether we need to
+    // step again according to the given stepUntil function.
+    StepFilter stepUntilFilter = new StepFilter() {
+      @Override
+      public List<String> getExcludedClasses() {
+        return stepFilter.getExcludedClasses();
+      }
+
+      @Override
+      public boolean skipLocation(JUnit3Wrapper.DebuggeeState debuggeeState, JUnit3Wrapper wrapper,
+          JUnit3Wrapper.Command.StepCommand stepCommand) {
+        if (stepFilter.skipLocation(debuggeeState, wrapper, stepCommand)) {
+          return true;
+        }
+        if (stepUntil.apply(debuggeeState) == Boolean.FALSE) {
+          // We did not reach the expected location so step again.
+          wrapper.enqueueCommandFirst(stepCommand);
+          return true;
+        }
+        return false;
+      }
+    };
+    return new JUnit3Wrapper.Command.StepCommand(stepKind, stepLevel, stepUntilFilter);
   }
 
   protected final JUnit3Wrapper.Command checkLocal(String localName) {
@@ -1512,23 +1538,14 @@
 
       class StepCommand implements Command {
 
-        private final byte stepDepth;
-        private final byte stepSize;
+        private final StepKind stepDepth;
+        private final StepLevel stepSize;
         private final StepFilter stepFilter;
 
-        /**
-         * A {@link Function} taking a {@link DebuggeeState} as input and returns {@code true} to
-         * stop stepping, {@code false} to continue.
-         */
-        private final Function<JUnit3Wrapper.DebuggeeState, Boolean> stepUntil;
-
-        public StepCommand(byte stepDepth,
-            byte stepSize, StepFilter stepFilter,
-            Function<DebuggeeState, Boolean> stepUntil) {
+        public StepCommand(StepKind stepDepth, StepLevel stepSize, StepFilter stepFilter) {
           this.stepDepth = stepDepth;
           this.stepSize = stepSize;
           this.stepFilter = stepFilter;
-          this.stepUntil = stepUntil;
         }
 
         @Override
@@ -1537,14 +1554,13 @@
           int stepRequestID;
           {
             EventBuilder eventBuilder = Event.builder(EventKind.SINGLE_STEP, SuspendPolicy.ALL);
-            eventBuilder.setStep(threadId, stepSize, stepDepth);
+            eventBuilder.setStep(threadId, stepSize.jdwpValue, stepDepth.jdwpValue);
             stepFilter.getExcludedClasses().stream().forEach(s -> eventBuilder.setClassExclude(s));
             ReplyPacket replyPacket = testBase.getMirror().setEvent(eventBuilder.build());
             stepRequestID = replyPacket.getNextValueAsInt();
             testBase.assertAllDataRead(replyPacket);
           }
-          testBase.events
-              .put(stepRequestID, new StepEventHandler(this, stepRequestID, stepFilter, stepUntil));
+          testBase.events.put(stepRequestID, new StepEventHandler(this, stepRequestID, stepFilter));
 
           // Resume all threads.
           testBase.resume();
@@ -1552,8 +1568,8 @@
 
         @Override
         public String toString() {
-          return String.format("step %s/%s", JDWPConstants.StepDepth.getName(stepDepth),
-              JDWPConstants.StepSize.getName(stepSize));
+          return String.format("step %s/%s", JDWPConstants.StepDepth.getName(stepDepth.jdwpValue),
+              JDWPConstants.StepSize.getName(stepSize.jdwpValue));
         }
       }
 
@@ -1607,17 +1623,14 @@
       private final JUnit3Wrapper.Command.StepCommand stepCommand;
       private final int stepRequestID;
       private final StepFilter stepFilter;
-      private final Function<DebuggeeState, Boolean> stepUntil;
 
       private StepEventHandler(
           JUnit3Wrapper.Command.StepCommand stepCommand,
           int stepRequestID,
-          StepFilter stepFilter,
-          Function<DebuggeeState, Boolean> stepUntil) {
+          StepFilter stepFilter) {
         this.stepCommand = stepCommand;
         this.stepRequestID = stepRequestID;
         this.stepFilter = stepFilter;
-        this.stepUntil = stepUntil;
       }
 
       @Override
@@ -1626,18 +1639,11 @@
         testBase.getMirror().clearEvent(EventKind.SINGLE_STEP, stepRequestID);
         testBase.events.remove(Integer.valueOf(stepRequestID));
 
-        // Do we need to step again ?
-        boolean repeatStep = false;
-        if (stepFilter
-            .skipLocation(testBase.getMirror(), testBase.getDebuggeeState().getLocation())) {
-          repeatStep = true;
-        } else if (stepUntil.apply(testBase.getDebuggeeState()) == Boolean.FALSE) {
-          repeatStep = true;
-        }
-        if (repeatStep) {
-          // In order to repeat the step now, we need to add it at the beginning of the queue.
-          testBase.enqueueCommandFirst(stepCommand);
-        }
+        // Let the filtering happen.
+        // Note: we don't need to know whether the location was skipped or not because we are
+        // going to process the next command(s) in the queue anyway.
+        stepFilter.skipLocation(testBase.getDebuggeeState(), testBase, stepCommand);
+
         super.handle(testBase);
       }
     }
@@ -1659,7 +1665,8 @@
     /**
      * Indicates whether the given location must be skipped.
      */
-    boolean skipLocation(VmMirror mirror, Location location);
+    boolean skipLocation(JUnit3Wrapper.DebuggeeState debuggeeState, JUnit3Wrapper wrapper,
+        JUnit3Wrapper.Command.StepCommand stepCommand);
 
     /**
      * A {@link StepFilter} that does not filter anything.
@@ -1672,7 +1679,8 @@
       }
 
       @Override
-      public boolean skipLocation(VmMirror mirror, Location location) {
+      public boolean skipLocation(JUnit3Wrapper.DebuggeeState debuggeeState, JUnit3Wrapper wrapper,
+          JUnit3Wrapper.Command.StepCommand stepCommand) {
         return false;
       }
     }
@@ -1687,7 +1695,7 @@
 
       @Override
       public List<String> getExcludedClasses() {
-        return Arrays.asList(
+        return ImmutableList.of(
             "com.sun.*",
             "java.*",
             "javax.*",
@@ -1707,8 +1715,10 @@
       }
 
       @Override
-      public boolean skipLocation(VmMirror mirror, Location location) {
-        // TODO(b/67225390) we also need to skip class loaders to act like IntelliJ.
+      public boolean skipLocation(JUnit3Wrapper.DebuggeeState debuggeeState, JUnit3Wrapper wrapper,
+          JUnit3Wrapper.Command.StepCommand stepCommand) {
+        VmMirror mirror = debuggeeState.getMirror();
+        Location location = debuggeeState.getLocation();
         // Skip synthetic methods.
         if (isLambdaMethod(mirror, location)) {
           // Lambda methods are synthetic but we do want to stop there.
@@ -1722,14 +1732,37 @@
           if (DEBUG_TESTS) {
             System.out.println("Skipping lambda class wrapper method");
           }
+          wrapper.enqueueCommandFirst(stepCommand);
           return true;
         }
         if (isSyntheticMethod(mirror, location)) {
           if (DEBUG_TESTS) {
             System.out.println("Skipping synthetic method");
           }
+          wrapper.enqueueCommandFirst(stepCommand);
           return true;
         }
+        if (isClassLoader(mirror, location)) {
+          if (DEBUG_TESTS) {
+            System.out.println("Skipping class loader");
+          }
+          wrapper.enqueueCommandFirst(
+              new JUnit3Wrapper.Command.StepCommand(StepKind.OUT, StepLevel.LINE, this));
+          return true;
+        }
+        return false;
+      }
+
+      private static boolean isClassLoader(VmMirror mirror, Location location) {
+        final long classLoaderClassID = mirror.getClassID("Ljava/lang/ClassLoader;");
+        assert classLoaderClassID != -1;
+        long classID = location.classID;
+        while (classID != 0) {
+          if (classID == classLoaderClassID) {
+            return true;
+          }
+          classID = mirror.getSuperclassId(classID);
+        }
         return false;
       }
 
@@ -1751,7 +1784,7 @@
         return classSig.contains("$$Lambda$");
       }
 
-      private boolean isLambdaMethod(VmMirror mirror, Location location) {
+      private static boolean isLambdaMethod(VmMirror mirror, Location location) {
         String methodName = mirror.getMethodName(location.classID, location.methodID);
         return methodName.startsWith("lambda$");
       }
diff --git a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
index 46ad815..713d042 100644
--- a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
@@ -22,10 +22,6 @@
 
   @Test
   public void testDefaultMethod() throws Throwable {
-    // TODO(b/67225390) Dalvik steps into class loader first.
-    Assume.assumeTrue("Dalvik suspends in class loader",
-        ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4));
-
     String debuggeeClass = "DebugInterfaceMethod";
     String parameterName = "msg";
     String localVariableName = "name";
@@ -38,16 +34,16 @@
     if (!supportsDefaultMethod()) {
       // We desugared default method. This means we're going to step through an extra (forward)
       // method first.
-      commands.add(stepInto());
+      commands.add(stepInto(INTELLIJ_FILTER));
     }
-    commands.add(stepInto());
+    commands.add(stepInto(INTELLIJ_FILTER));
     commands.add(checkLine(SOURCE_FILE, 9));
     // TODO(shertz) we should see the local variable this even when desugaring.
     if (supportsDefaultMethod()) {
       commands.add(checkLocal("this"));
     }
     commands.add(checkLocal(parameterName));
-    commands.add(stepOver());
+    commands.add(stepOver(INTELLIJ_FILTER));
     commands.add(checkLocal(parameterName));
     commands.add(checkLocal(localVariableName));
     // TODO(shertz) check current method name ?
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 6a297cd..db41173 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -3,14 +3,9 @@
 // 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.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -68,28 +63,18 @@
     final String innerClassName = minifiedNames ? "a" : "Minified$Inner";
     final String innerMethodName = minifiedNames ? "a" : "innerTest";
     final String innerSignature = "()I";
-    List<Command> commands =
-        new ArrayList<>(
-            Arrays.asList(
-                breakpoint(className, methodName, signature),
-                run(),
-                checkMethod(className, methodName, signature),
-                checkLine(SOURCE_FILE, 14),
-                stepOver(),
-                checkMethod(className, methodName, signature),
-                checkLine(SOURCE_FILE, 15),
-                stepInto()));
-    // Dalvik first enters ClassLoader, step over it.
-    // See also b/67225390.
-    if (ToolHelper.getDexVm() == DexVm.ART_4_4_4_HOST) {
-      commands.add(stepOver());
-    }
-    commands.addAll(
-        Arrays.asList(
-            checkMethod(innerClassName, innerMethodName, innerSignature),
-            checkLine(SOURCE_FILE, 8),
-            run()));
-    runDebugTestR8(className, commands);
+    runDebugTestR8(className,
+        breakpoint(className, methodName, signature),
+        run(),
+        checkMethod(className, methodName, signature),
+        checkLine(SOURCE_FILE, 14),
+        stepOver(INTELLIJ_FILTER),
+        checkMethod(className, methodName, signature),
+        checkLine(SOURCE_FILE, 15),
+        stepInto(INTELLIJ_FILTER),
+        checkMethod(innerClassName, innerMethodName, innerSignature),
+        checkLine(SOURCE_FILE, 8),
+        run());
   }
 
   @Test