假设我们有如下代码:
data class TestException(val value: String) // 1
// 2
fun main() { // 3
// 4
val strings = listOf("A") // 5
TestException(value = strings[1]) // 6
// 7
} // 8
此代码的输出看起来一致(我们在第 行抛出了异常6
):
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at java.base/java.util.Collections$SingletonList.get(Collections.java:5180)
at MainKt.main(Main.kt:6)
at MainKt.main(Main.kt)
现在让我们修改行号6
,这样代码看起来如下:
data class TestException(val value: String) // 1
// 2
fun main() { // 3
// 4
val strings = listOf("A") // 5
TestException(value = strings.first { it != "A" }) // 6
// 7
} // 8
现在异常提到的行12
是代码最后一行后面 4 行。
Exception in thread "main" java.util.NoSuchElementException: Collection contains no element matching the predicate.
at MainKt.main(Main.kt:12)
at MainKt.main(Main.kt)
你能解释一下其中的原理吗?
已知问题
这是一个已知问题:KT-8628 – 异常堆栈跟踪中的行号不正确。该错误于 2015 年报告。不幸的是,它最终被关闭为“第三方问题”,其中第三方是 JVM。此问题的 OpenJDK 问题是JDK-8272568 – 使用 Kotlin 时堆栈跟踪中的行号不正确,但它被关闭为与更早的问题JDK-4972961 – 在堆栈跟踪中使用 SourceDebugExtension重复。但随后该问题被关闭为“无法修复”。因此,看起来这个问题不会很快得到解决。
问题解释
问题与函数有关
inline
。当您调用这样的函数时,该函数的代码将内联到您自己的代码中。但这会遇到一个问题。类文件中的某些字节码现在来自不同的源/类文件。需要以某种方式记录该信息。否则,堆栈跟踪无法准确地表示源代码;同样,调试器无法确定某些代码来自何处,从而使单步执行代码变得很尴尬。Kotlin 开发人员似乎决定使用SourceDebugExtension属性作为解决方案。但是,JVM 在生成堆栈跟踪时不会参考该属性,这显然会导致涉及内联函数时行号错误。
请注意
List::first((E)->Boolean)
,您调用的(扩展)函数是一个内联函数。个人感想
不过我确实想知道为什么LineNumberTable属性一开始就不能更准确。在我看来,出于行号的目的,编译器可以有效地忽略调用内联函数的事实。以这样的方式定义表,即从内联函数“之外”抛出的异常看起来像是在调用内联函数的行上抛出的。至于传递给内联函数的 lambda 中的代码抛出的异常,lambda 位于源代码中,因此可以像平常一样记录行号。调试器等工具可以继续使用SourceDebugExtension属性来获取更多信息。但我不是这个问题的专家。也许这样做会导致其他问题。