Say we have the codelike this:
data class TestException(val value: String) // 1
// 2
fun main() { // 3
// 4
val strings = listOf("A") // 5
TestException(value = strings[1]) // 6
// 7
} // 8
The output of this code looks consistent (we have exception thrown on line 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)
Let's now modify the line number 6
so the code would now look like:
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
Now the exception mentions line 12
that is 4 lines behind the last line of the code.
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)
Can you please explain the mechanic?
This is a known issue: KT-8628 – Line numbers are incorrect in exception stack trace. The bug was reported back in 2015. Unfortunately, it was eventually closed as a "Third Party Problem", where the third-party is the JVM. The OpenJDK issue for this was JDK-8272568 – Incorrect line numbers in stack traces when using Kotlin, but that was closed as a duplicate of a much older issue, JDK-4972961 – Use SourceDebugExtension in stack traces. But then that issue was closed as "won't fix". So, it doesn't look like this problem is going to be fixed any time soon.
The problem has to do with inline
functions. When you call such a function, that function's code is inlined into your own code. But this runs into a problem. Some of the byte-code in a class file is now from a different source/class file. That information needs to be recorded somehow. Otherwise, stack traces can't accurately represent the source code; similarly, debuggers can't determine where certain code comes from, making stepping through the code awkward.
It seems the Kotlin developers decided to use the SourceDebugExtension attribute as a solution. However, the JVM does not consult that attribute when generating stack traces, and that apparently leads to the line numbers being wrong when inline functions are involved.
Note the Iterable::first((E)->Boolean)
(extension) function you're calling is an inline function.
Some tools (like IntelliJ) know how to properly read the stack trace, despite the wrong line numbers. See the answer by @k314159.
Though I do wonder why the LineNumberTable attribute couldn't be more accurate in the first place. It seems to me, that for the purposes of line numbers, the compiler could effectively ignore the fact an inline function was called. Define the table in such a way that exceptions thrown "out of" the inline function look like they were thrown on the line the inline function was called. As for exceptions thrown by code in a lambda passed to an inline function, well that lambda is in the source code, and so the line numbers could be recorded like normal. Tools like debuggers could continue to use the SourceDebugExtension attribute for more information. But I'm not an expert on the problem. Perhaps doing it that way causes other problems down the line.