为啥 Intellij 的调试器会为此 Scala 代码两次命中断点

Posted

技术标签:

【中文标题】为啥 Intellij 的调试器会为此 Scala 代码两次命中断点【英文标题】:Why Intellij's debugger hits breakpoint twice for this Scala code为什么 Intellij 的调试器会为此 Scala 代码两次命中断点 【发布时间】:2022-01-14 16:42:06 【问题描述】:

当我在 if 语句中设置调试点并启用“日志:断点命中消息”时,用于以下 scala 代码

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")
    
  

我在控制台中得到以下输出。问题是为什么这个断点会被命中两次?

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

但是对于类似的Java代码,断点只被命中一次。

        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");
        

我得到以下输出

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

这是 Intellij 调试器中的错误,还是 Scala 编程语言的一个特性?

IntelliJ IDEA 2021.3 爪哇 - 11 斯卡拉 - 2.12.15

【问题讨论】:

【参考方案1】:

这是 scalac 字节码生成的一个特点。由于某种原因,它在返回指令之前为调试器生成了一个额外的位置并将其放在该行上。您可以在 IDE 中调用“显示字节码”操作并看到有 2 个LINENUMBER 8 条目:

// 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 编译器没有添加这个并且断点按预期工作。

【讨论】:

感谢@niktrop,我正在调试一个 Scala 代码,其中包含理解/收益、期货等。我认为我的代码被调用了两次并试图找出根本原因。最终我怀疑,也许代码没有执行两次,但断点多次命中并以简单的方法进行测试。确实是这样。

以上是关于为啥 Intellij 的调试器会为此 Scala 代码两次命中断点的主要内容,如果未能解决你的问题,请参考以下文章

intellij idea新建时为啥找不到scala class

IntelliJ:Scala 测试中的断点不会被命中

为啥 IntelliJ 在远程调试时跳过断点

在IntelliJ中调试sbt项目

在 Scala 中替代 println

在 IntelliJ 中评估返回值的表达式