Accessing source line number at runtime


#1

I’m writing something that requires knowing the source line number where a certain function is called. In Java you could use Throwable.getStacktrace() or Thread.currentThread().getStacktrace(). This still works in Kotlin if you are not using any inline methods.


fun printLineNumber() {
    Throwable().printStackTrace()
}

fun normalFunction() {

}

inline fun inlineFunction() {

}

fun test1() {
    inlineFunction()
    printLineNumber()
}

fun test2() {
    normalFunction()
    printLineNumber()
}

fun test3() {
    println()
    printLineNumber()
}

fun main(vararg args: String) {
    test1()
    test2()
    test3()
}

Output is

// test1()
java.lang.Throwable
	at TestKt.printLineNumber(Test.kt:3)
	at TestKt.test1(Test.kt:12)
	at TestKt.main(Test.kt:30)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

// test(2)
java.lang.Throwable
	at TestKt.printLineNumber(Test.kt:3)
	at TestKt.test2(Test.kt:21)
	at TestKt.main(Test.kt:31)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

// test3()
java.lang.Throwable
	at TestKt.printLineNumber(Test.kt:3)
	at TestKt.test3(Test.kt:37)
	at TestKt.main(Test.kt:32)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

#2

@raniejade, feel free to file a bug.
For your case you can use next workaround: explicitly define ‘return’ statement within last inline function instruction:

inline fun inlineFunction() {
“1”; return
}

From 1.0.3 version additional debug strata will be avaliable in source mapping so you can try map adiitional linenumbers (e.g. Test.kt:37) on call site linenumbers


#3

Is there any update about it? I tried it with 1.0.3 but it is not working as well.


#4

What do you expect to be working? When you use inline functions, line numbers in stacktraces will always contain lines outside of the range of the original file; this is required in order to support stepping through inline functions. The additional debug strata mentioned by Michael above requires additional processing by your code; it will not affect the line numbers reported in regular Java stacktraces.


#5

Can you elaborate what is the additional debug starta? not sure I understand.
Can you elaborate how the workaround with return above is working?
I understand not to expect line numbers right, and not that I understand how the compiler works but isn’t something like source code attachment can help?


#6

Additional debug strata is additional information stored in the .class file by the Kotlin compiler and accessed by IntelliJ IDEA, which allows us to associate multiple line numbers with a single bytecode location (the line number from which an inline function was called and the line number inside the inline function).

Source code attachment has nothing to do with this. We need to be able to identify the line number corresponding to every location in the bytecode, and when you use inline functions, a single .class file contains lines from multiple source files, so we have to use synthetic line numbers to identify line numbers coming from external files.


#7

Unfortunately the Java class format only allows for a single source file to be attached to a class file. While it is possible to embed additional values the standard stacktrace machinery doesn’t know about it. The same holds for debuggers that don’t know about kotlin. As the code does have source lines, emitting line numbers outside the valid range into the debug information at least signals that the line is not actually in the file. Of course if all the code lives in the same file, the out of range line numbers are not needed.


#8

Can you explain that?


#9

This is a bit off topic, but maybe it is worth it. I wonder whether it is possible for Kotlin to include the values of temporary arguments and method vars in the stack trace. We always had this earlier in the old days of Smalltalk regardless of what Smalltalk implementation was used, but with Java this was all gone.

I don’t know whether Kotlin has to rely here on what it gets from the JVM or has additional means through the Kotlin runtime to have this. I think it would be of great use when analysing stack traces.


#10

The stacktrace formatting is entirely done by the JVM; there’s nothing we could do to change it from our side.


#11

The additional debug strata mentioned by Michael above requires additional processing by your code

Is there an API we could use to access the additional debug strata?


#12

We access the data through the JDI API: https://docs.oracle.com/javase/7/docs/jdk/api/jpda/jdi/com/sun/jdi/Location.html

I’m not sure whether there’s any API that allows to access this information directly from the running application and not from the debugger connected to it. However, you definitely have the possibility to parse your .class files manually and retrieve the information from there.