diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt index a52f19a3412..8e95ee257a2 100644 --- a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/SafeContinuationJvm.kt @@ -8,6 +8,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater import kotlin.* import kotlin.coroutines.intrinsics.CoroutineSingletons.* import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED +import kotlin.coroutines.jvm.internal.CoroutineStackFrame @PublishedApi @SinceKotlin("1.3") @@ -15,7 +16,7 @@ internal actual class SafeContinuation internal actual constructor( private val delegate: Continuation, initialResult: Any? -) : Continuation { +) : Continuation, CoroutineStackFrame { @PublishedApi internal actual constructor(delegate: Continuation) : this(delegate, UNDECIDED) @@ -60,4 +61,15 @@ internal actual constructor( else -> result // either COROUTINE_SUSPENDED or data } } + + // --- CoroutineStackFrame implementation + + public override val callerFrame: CoroutineStackFrame? + get() = delegate as? CoroutineStackFrame + + override fun getStackTraceElement(): StackTraceElement? = + null + + override fun toString(): String = + "SafeContinuation for $delegate" } diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt index adb2b7be709..2bd632769a5 100644 --- a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt @@ -16,7 +16,7 @@ internal abstract class BaseContinuationImpl( // This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet // it has a public getter (since even untrusted code is allowed to inspect its call stack). public val completion: Continuation? -) : Continuation, Serializable { +) : Continuation, CoroutineStackFrame, Serializable { // This implementation is final. This fact is used to unroll resumeWith recursion. public final override fun resumeWith(result: SuccessOrFailure) { // Invoke "resume" debug probe only once, even if previous frames are "resumed" in the loop below, too @@ -63,9 +63,16 @@ internal abstract class BaseContinuationImpl( throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden") } - public override fun toString(): String { - return "Continuation at ${getStackTraceElement() ?: this::class.java.name}" - } + public override fun toString(): String = + "Continuation at ${getStackTraceElement() ?: this::class.java.name}" + + // --- CoroutineStackFrame implementation + + public override val callerFrame: CoroutineStackFrame? + get() = completion as? CoroutineStackFrame + + public override fun getStackTraceElement(): StackTraceElement? = + getStackTraceElementImpl() } @SinceKotlin("1.3") diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/CoroutineStackFrame.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/CoroutineStackFrame.kt new file mode 100644 index 00000000000..c75383eaf31 --- /dev/null +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/CoroutineStackFrame.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package kotlin.coroutines.jvm.internal + +import kotlin.coroutines.* + +/** + * Represents one frame in the coroutine call stack for debugger. + * This interface is implemented by compiler-generated implementations of + * [Continuation] interface. + */ +public interface CoroutineStackFrame { + /** + * Returns a reference to the stack frame of the caller of this frame, + * that is a frame before this frame in coroutine call stack. + * The result is `null` for the first frame of coroutine. + */ + public val callerFrame: CoroutineStackFrame? + + /** + * Returns stack trace element that correspond to this stack frame. + * The result is `null` if the stack trace element is not available for this frame. + * In this case, the debugger represents this stack frame using the + * result of [toString] function. + */ + public fun getStackTraceElement(): StackTraceElement? +} \ No newline at end of file diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugMetadata.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugMetadata.kt index aeb83f02a84..d7510c0bb84 100644 --- a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugMetadata.kt +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugMetadata.kt @@ -31,26 +31,30 @@ internal annotation class DebugMetadata( * The coroutine can be either running coroutine, that calls the function on its continuation and obtaining * the information about current file and line number, or, more likely, the function is called to produce accurate stack traces of * suspended coroutine. + * + * The result is `null` when debug metadata is not available. */ @SinceKotlin("1.3") @JvmName("getStackTraceElement") -internal fun BaseContinuationImpl.getStackTraceElement(): StackTraceElement? { - val debugMetadata = getDebugMetadataAnnotation() +internal fun BaseContinuationImpl.getStackTraceElementImpl(): StackTraceElement? { + val debugMetadata = getDebugMetadataAnnotation() ?: return null val label = getLabel() val fileName = if (label < 0) "" else debugMetadata.sourceFiles[label] val lineNumber = if (label < 0) -1 else debugMetadata.lineNumbers[label] return StackTraceElement(debugMetadata.className, debugMetadata.methodName, fileName, lineNumber) } -private fun BaseContinuationImpl.getDebugMetadataAnnotation(): DebugMetadata { - return javaClass.annotations.filterIsInstance()[0] -} +private fun BaseContinuationImpl.getDebugMetadataAnnotation(): DebugMetadata? = + javaClass.getAnnotation(DebugMetadata::class.java) -private fun BaseContinuationImpl.getLabel(): Int { - val field = javaClass.getDeclaredField("label") ?: return -1 - field.isAccessible = true - return (field.get(this) as? Int ?: 0) - 1 -} +private fun BaseContinuationImpl.getLabel(): Int = + try { + val field = javaClass.getDeclaredField("label") + field.isAccessible = true + (field.get(this) as? Int ?: 0) - 1 + } catch (e: Exception) { // NoSuchFieldException, SecurityException, or IllegalAccessException + -1 + } /** * Returns an array of spilled variable names and continuation's field names where the variable has been spilled. @@ -60,10 +64,13 @@ private fun BaseContinuationImpl.getLabel(): Int { * * The function is for debugger to use, thus it returns simplest data type possible. * This function should only be called on suspended coroutines to get accurate mapping. + * + * The result is `null` when debug metadata is not available. */ @SinceKotlin("1.3") -internal fun BaseContinuationImpl.getSpilledVariableFieldMapping(): Array { - val debugMetadata = getDebugMetadataAnnotation() +@JvmName("getSpilledVariableFieldMapping") +internal fun BaseContinuationImpl.getSpilledVariableFieldMapping(): Array? { + val debugMetadata = getDebugMetadataAnnotation() ?: return null val res = arrayListOf() val label = getLabel() for ((i, labelOfIndex) in debugMetadata.indexToLabel.withIndex()) { diff --git a/libraries/stdlib/coroutines/jvm/test/DebugMetadataTest.kt b/libraries/stdlib/coroutines/jvm/test/DebugMetadataTest.kt index 87f783cc88f..c89f5e37389 100644 --- a/libraries/stdlib/coroutines/jvm/test/DebugMetadataTest.kt +++ b/libraries/stdlib/coroutines/jvm/test/DebugMetadataTest.kt @@ -46,18 +46,18 @@ class DebugMetadataTest { StackTraceElement("SomeClass", "testMethod", "test.kt", 10), myContinuation.getStackTraceElement() ) - assertEquals(listOf("L$1", "a", "L$2", "b"), myContinuation.getSpilledVariableFieldMapping().toList()) + assertEquals(listOf("L$1", "a", "L$2", "b"), myContinuation.getSpilledVariableFieldMapping()!!.toList()) myContinuation.label = 2 assertEquals( StackTraceElement("SomeClass", "testMethod", "test1.kt", 2), myContinuation.getStackTraceElement() ) - assertEquals(listOf("L$1", "b", "L$2", "c"), myContinuation.getSpilledVariableFieldMapping().toList()) + assertEquals(listOf("L$1", "b", "L$2", "c"), myContinuation.getSpilledVariableFieldMapping()!!.toList()) myContinuation.label = 3 assertEquals( StackTraceElement("SomeClass", "testMethod", "test.kt", 11), myContinuation.getStackTraceElement() ) - assertEquals(listOf("L$1", "c"), myContinuation.getSpilledVariableFieldMapping().toList()) + assertEquals(listOf("L$1", "c"), myContinuation.getSpilledVariableFieldMapping()!!.toList()) } }