在 java 编译器中标记行时出现问题

Mat*_*osa 4 java bytecode javac javap

我是一名硕士生,正在研究静态分析。在我的一项测试中,我遇到了在 java 编译器中标记行的问题。

我有以下java代码:

 226:   String json = "/org/elasticsearch/index/analysis/commongrams/commongrams_query_mode.json";
 227:   Settings settings = Settings.settingsBuilder()
 228:           .loadFromStream(json, getClass().getResourceAsStream(json))
 229:           .put("path.home", createHome())
 230:           .build();
Run Code Online (Sandbox Code Playgroud)

编译此代码并执行命令时javap -p -v CLASSNAME,我得到一个表,其中包含字节码中每条指令的源代码的相应行。

见下图:

字节码表

问题在于,在对该.put (" path.home ", createHome ())方法的调用中,字节码基本上生成四个指令:

19: anewarray  
24: ldc - String path.home
30: invokespecial - createHome
34: invokevirtual - put

Run Code Online (Sandbox Code Playgroud)

前两行标记为第 228 行(错误),后两行标记为第 229 行(正确)。

见下图:

字节码表

这是该方法的原始实现.put("path.home", createHome())

     public Builder put(Object... settings) {
        if (settings.length == 1) {
            // support cases where the actual type gets lost down the road...
            if (settings[0] instanceof Map) {
                //noinspection unchecked
                return put((Map) settings[0]);
            } else if (settings[0] instanceof Settings) {
                return put((Settings) settings[0]);
            }
        }
        if ((settings.length % 2) != 0) {
            throw new IllegalArgumentException("array settings of key + value order doesn't hold correct number of arguments (" + settings.length + ")");
        }
        for (int i = 0; i < settings.length; i++) {
            put(settings[i++].toString(), settings[i].toString());
        }
        return this;
    }
Run Code Online (Sandbox Code Playgroud)

我已经尝试使用 Oracle-JDK v8 和 Open-JDK v16 编译代码并得到两个结果。

put()我还通过删除参数来更改方法来进行测试。编译此代码时,没有出现标记行的问题。

我想知道为什么字节码指令将行映射229: .put (" path.home ", createHome ())到java源代码中原始行以外的行上?有谁知道这是否是故意的?

Hol*_*ger 6

这是连接方式,行号关联存储在类文件和javac编译器的历史记录中。

\n

号表仅包含将行号与标记其开始的代码位置相关联的条目。因此,假设该位置之后的所有指令都属于同一行,直到表中明确提到的下一个位置。

\n

由于详细信息会占用空间,并且规范不要求行号表具有特定的精度,因此编译器供应商对于包含哪些详细信息做出了不同的决定。

\n

过去,即直到Java\xc2\xa07,只生成语句javac开头的行号表条目,所以当我用Java\xc2\xa07\xe2\x80\x99s编译以下代码时javac

\n
String settings = new StringBuilder() // this is line 7 in my .java file\n    .append(\'a\')\n    .append(\n      5\n      +\n      "".length())\n    .toString();\n
Run Code Online (Sandbox Code Playgroud)\n

我得到类似的东西

\n
stack=3, locals=2, args_size=1\n   0: new           #2                  // class java/lang/StringBuilder\n   3: dup\n   4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V\n   7: bipush        97\n   9: invokevirtual #4                  // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;\n  12: iconst_5\n  13: ldc           #5                  // String\n  15: invokevirtual #6                  // Method java/lang/String.length:()I\n  18: iadd\n  19: invokevirtual #7                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;\n  22: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;\n  25: astore_1\n  26: return\nLineNumberTable:\n  line 7: 0\n  line 14: 26\n
Run Code Online (Sandbox Code Playgroud)\n

这将导致属于该语句的所有指令仅与第 7 行关联。

\n

这已经被认为太少了,因此从 Java\xc2\xa08 开始,生成方法调用javac的附加条目为跨多行的表达式内的因此,当我使用 Java\xc2\xa08 或更新版本编译相同的代码时,我得到

\n
stack=3, locals=2, args_size=1\n   0: new           #2                  // class java/lang/StringBuilder\n   3: dup\n   4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V\n   7: bipush        97\n   9: invokevirtual #4                  // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;\n  12: iconst_5\n  13: ldc           #5                  // String\n  15: invokevirtual #6                  // Method java/lang/String.length:()I\n  18: iadd\n  19: invokevirtual #7                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;\n  22: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;\n  25: astore_1\n  26: return\nLineNumberTable:\n  line 7: 0\n  line 8: 9\n  line 12: 15\n  line 9: 19\n  line 13: 22\n  line 14: 26\n
Run Code Online (Sandbox Code Playgroud)\n

请注意每个附加条目(与 Java\xc2\xa07 版本相比)如何指向调用指令,以确保方法调用与正确的行号关联。这极大地改善了异常堆栈跟踪以及步骤调试。

\n

没有显式条目的非调用指令仍将与具有条目的最接近的先前代码位置相关联。

\n

因此,与常量bipush 97对应的指令与\'a\'第 7 行相关联,因为只有append使用该常量的后续调用才有将其与第 8 行关联的显式条目。

\n

下一个表达式 的后果5 + "".length()更加引人注目。

\n

iconst_5使用常量和 的指令ldc [""]与第 8 行(上一次调用的位置)相关联append,而iadd实际上属于和常量+之间的运算符的指令与第 12 行相关联,作为最近的调用指令调用时得到了明确的行号。5""length()

\n

为了进行比较,Eclipse 编译相同代码的方式如下:

\n
stack=3, locals=2, args_size=1\n   0: new           #20                 // class java/lang/StringBuilder\n   3: dup\n   4: invokespecial #22                 // Method java/lang/StringBuilder."<init>":()V\n   7: bipush        97\n   9: invokevirtual #23                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;\n  12: iconst_5\n  13: ldc           #27                 // String\n  15: invokevirtual #29                 // Method java/lang/String.length:()I\n  18: iadd\n  19: invokevirtual #35                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;\n  22: invokevirtual #38                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;\n  25: astore_1\n  26: return\nLineNumberTable:\n  line 6: 0\n  line 7: 7\n  line 9: 12\n  line 11: 13\n  line 9: 18\n  line 8: 19\n  line 12: 22\n  line 6: 25\n  line 13: 26\n
Run Code Online (Sandbox Code Playgroud)\n

Eclipse 编译器没有 \xe2\x80\ javacx99s 历史记录,而是被设计为首先为表达式生成行号条目。我们可以看到它将属于调用表达式的第一条指令不是调用指令)与右侧的行(即bipush 97forappend(\'a\')ldc [""]for )相关联"".length()

\n

此外,它还具有iconst_5iadd、 和 的附加条目astore_1,以将它们与正确的行关联起来。当然,这种更高的精度也会导致类文件稍大一些。

\n