Search code examples
javascaladebuggingintellij-ideabreakpoints

Why Intellij's debugger hits breakpoint twice for this Scala code


For following scala code when I set a debug point at if statement and enable "Log: Breakpoint hit message"

object App1 {
  def main(args: Array[String]): Unit = {
    iftest()
  }
  def iftest(): Unit = {
    val setA: Set[String] = Set("a", "b", "c");
    var setB: Set[String] = Set("d", "e", "f")
    if(setA.size > setB.size){ //here break point at line 8
      println("bigger")
    }
  }
}

I got the following output in console. The question is why this breakpoint is hit twice?

Breakpoint reached at com.eguller.App1$.iftest(App1.scala:8)
Breakpoint reached at com.eguller.App1$.iftest(App1.scala:8)

But for following similar Java code, the breakpoint is hit only once.

        Set<String> set1 = new HashSet<>();
        set1.add("1");
        set1.add("2");

        Set<String> set2 = new HashSet<>();
        set2.add("a");
        set2.add("b");

        if(set1.size() > set2.size()){ //here break point at line 8
            System.out.println("size different");
        }

I got the following output

Breakpoint reached at com.eguller.JApp.main(JApp.java:8)

Is it a bug in Intellij debugger, or is it a feature of Scala programming language?

IntelliJ IDEA 2021.3 Java - 11 Scala - 2.12.15


Solution

  • This is a peculiarity of scalac bytecode generation. For some reason it generates an additional position for the debugger just before the return instruction and puts it on that line. You may invoke "Show bytecode" action in the IDE and see that there are 2 LINENUMBER 8 entries:

    // access flags 0x1
    public iftest()V
     L0
      LINENUMBER 6 L0
      GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
      INVOKEVIRTUAL scala/Predef$.Set ()Lscala/collection/immutable/Set$;
      GETSTATIC scala/runtime/ScalaRunTime$.MODULE$ : Lscala/runtime/ScalaRunTime$;
      ICONST_3
      ANEWARRAY java/lang/String
      DUP
      ICONST_0
      LDC "a"
      AASTORE
      DUP
      ICONST_1
      LDC "b"
      AASTORE
      DUP
      ICONST_2
      LDC "c"
      AASTORE
      CHECKCAST [Ljava/lang/Object;
      INVOKEVIRTUAL scala/runtime/ScalaRunTime$.wrapRefArray ([Ljava/lang/Object;)Lscala/collection/immutable/ArraySeq;
      INVOKEVIRTUAL scala/collection/immutable/Set$.apply (Lscala/collection/immutable/Seq;)Ljava/lang/Object;
      CHECKCAST scala/collection/immutable/Set
      ASTORE 1
     L1
      LINENUMBER 7 L1
      GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
      INVOKEVIRTUAL scala/Predef$.Set ()Lscala/collection/immutable/Set$;
      GETSTATIC scala/runtime/ScalaRunTime$.MODULE$ : Lscala/runtime/ScalaRunTime$;
      ICONST_3
      ANEWARRAY java/lang/String
      DUP
      ICONST_0
      LDC "d"
      AASTORE
      DUP
      ICONST_1
      LDC "e"
      AASTORE
      DUP
      ICONST_2
      LDC "f"
      AASTORE
      CHECKCAST [Ljava/lang/Object;
      INVOKEVIRTUAL scala/runtime/ScalaRunTime$.wrapRefArray ([Ljava/lang/Object;)Lscala/collection/immutable/ArraySeq;
      INVOKEVIRTUAL scala/collection/immutable/Set$.apply (Lscala/collection/immutable/Seq;)Ljava/lang/Object;
      CHECKCAST scala/collection/immutable/Set
      ASTORE 2
     L2
      LINENUMBER 8 L2
      ALOAD 1
      INVOKEINTERFACE scala/collection/immutable/Set.size ()I (itf)
      ALOAD 2
      INVOKEINTERFACE scala/collection/immutable/Set.size ()I (itf)
      IF_ICMPLE L3
     L4
      LINENUMBER 9 L4
      GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
      LDC "bigger"
      INVOKEVIRTUAL scala/Predef$.println (Ljava/lang/Object;)V
      GOTO L3
     L3
      LINENUMBER 8 L3
     FRAME APPEND [scala/collection/immutable/Set scala/collection/immutable/Set]
      RETURN
     L5
      LOCALVARIABLE setA Lscala/collection/immutable/Set; L1 L3 1
      LOCALVARIABLE setB Lscala/collection/immutable/Set; L2 L3 2
      LOCALVARIABLE this LApp1$; L0 L5 0
      MAXSTACK = 6
      MAXLOCALS = 3
    

    Scala 3 compiler doesn't add this and breakpoints work as expected.