Скажем, у нас есть такой код:
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 вообще не может быть более точным. Мне кажется, что для номеров строк компилятор мог бы эффективно игнорировать тот факт, что была вызвана встроенная функция. Определите таблицу таким образом, чтобы исключения, возникающие «вне» встроенной функции, выглядели так, как будто они были созданы в строке, в которой была вызвана встроенная функция. Что касается исключений, вызываемых кодом в лямбде, переданной встроенной функции, то эта лямбда находится в исходном коде, поэтому номера строк могут быть записаны как обычно. Такие инструменты, как отладчики, могут продолжать использовать атрибут SourceDebugExtension для получения дополнительной информации. Но я не эксперт в этой проблеме. Возможно, такой подход вызовет другие проблемы в будущем.
Блестящий анализ