[coroutine][debug] AsyncStackTraceProvider refactored
This commit is contained in:
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.xdebugger.frame.XNamedValue
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
|
||||
class AsyncStackTraceContext(
|
||||
val context: ExecutionContext,
|
||||
val method: Method,
|
||||
private val debugMetadataKtType: ClassType
|
||||
) {
|
||||
|
||||
fun getAsyncStackTraceForSuspendLambda() : List<StackFrameItem>? {
|
||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||
return null
|
||||
}
|
||||
|
||||
val thisObject = context.frameProxy.thisObject() ?: return null
|
||||
val thisType = thisObject.referenceType()
|
||||
|
||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return collectFrames(thisObject)
|
||||
}
|
||||
|
||||
fun getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
||||
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||
context.keepReference(continuation)
|
||||
|
||||
return collectFrames(continuation)
|
||||
}
|
||||
|
||||
private fun collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
||||
val frames = mutableListOf<StackFrameItem>()
|
||||
collectFramesRecursively(continuation, frames)
|
||||
return frames
|
||||
}
|
||||
|
||||
private fun collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||
|
||||
val location = getLocation(continuation)
|
||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||
|
||||
if (location != null) {
|
||||
consumer += StackFrameItem(location, spilledVariables)
|
||||
}
|
||||
|
||||
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
||||
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
||||
collectFramesRecursively(completion, consumer)
|
||||
}
|
||||
|
||||
private fun getLocation(continuation: ObjectReference): Location? {
|
||||
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
||||
"getStackTraceElement",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
||||
?: return null
|
||||
|
||||
context.keepReference(stackTraceElement)
|
||||
|
||||
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
||||
?: return null
|
||||
|
||||
fun getValue(name: String, desc: String): Value? {
|
||||
val method = stackTraceElementType.methodsByName(name, desc).single()
|
||||
return context.invokeMethod(stackTraceElement, method, emptyList())
|
||||
}
|
||||
|
||||
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
||||
|
||||
val locationClass = context.findClassSafe(className) ?: return null
|
||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||
}
|
||||
|
||||
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
||||
"getSpilledVariableFieldMapping",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
||||
?: return null
|
||||
|
||||
context.keepReference(rawSpilledVariables)
|
||||
|
||||
val length = rawSpilledVariables.length() / 2
|
||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
||||
|
||||
for (index in 0 until length) {
|
||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
||||
|
||||
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
||||
override fun calcValueName() = variableName
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||
}
|
||||
|
||||
spilledVariables += JavaValue.create(
|
||||
null,
|
||||
valueDescriptor,
|
||||
context.evaluationContext,
|
||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
return spilledVariables
|
||||
}
|
||||
|
||||
private tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
||||
return type
|
||||
}
|
||||
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-148
@@ -5,35 +5,20 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.xdebugger.frame.XNamedValue
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||
private companion object {
|
||||
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
||||
|
||||
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
||||
return type
|
||||
}
|
||||
|
||||
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
return hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) }
|
||||
}
|
||||
@@ -51,146 +36,30 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
|
||||
return null
|
||||
}
|
||||
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
||||
val context = ExecutionContext(evaluationContext, frameProxy)
|
||||
val context = createExecutionContext(suspendContext, frameProxy)
|
||||
|
||||
// DebugMetadataKt not found, probably old kotlin-stdlib version
|
||||
val debugMetadataKtType = context.findClassSafe(DEBUG_METADATA_KT) ?: return null
|
||||
val debugMetadataKtType = findDebugMetadata(context) ?: return null
|
||||
|
||||
val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType)
|
||||
return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction()
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
|
||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||
return null
|
||||
}
|
||||
private fun findDebugMetadata(context: ExecutionContext): ClassType? = context.findClassSafe(DEBUG_METADATA_KT)
|
||||
|
||||
val thisObject = context.frameProxy.thisObject() ?: return null
|
||||
val thisType = thisObject.referenceType()
|
||||
|
||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return collectFrames(thisObject)
|
||||
private fun createExecutionContext(
|
||||
suspendContext: SuspendContextImpl,
|
||||
frameProxy: StackFrameProxyImpl
|
||||
): ExecutionContext {
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
||||
return ExecutionContext(evaluationContext, frameProxy)
|
||||
}
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
||||
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||
context.keepReference(continuation)
|
||||
|
||||
return collectFrames(continuation)
|
||||
internal fun ExecutionContext.findClassSafe(className: String): ClassType? {
|
||||
return try {
|
||||
findClass(className) as? ClassType
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
||||
val frames = mutableListOf<StackFrameItem>()
|
||||
collectFramesRecursively(continuation, frames)
|
||||
return frames
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||
|
||||
val location = getLocation(continuation)
|
||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||
|
||||
if (location != null) {
|
||||
consumer += StackFrameItem(location, spilledVariables)
|
||||
}
|
||||
|
||||
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
||||
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
||||
collectFramesRecursively(completion, consumer)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? {
|
||||
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
||||
"getStackTraceElement",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
||||
?: return null
|
||||
|
||||
context.keepReference(stackTraceElement)
|
||||
|
||||
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
||||
?: return null
|
||||
|
||||
fun getValue(name: String, desc: String): Value? {
|
||||
val method = stackTraceElementType.methodsByName(name, desc).single()
|
||||
return context.invokeMethod(stackTraceElement, method, emptyList())
|
||||
}
|
||||
|
||||
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
||||
|
||||
val locationClass = context.findClassSafe(className) ?: return null
|
||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||
}
|
||||
|
||||
fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
||||
"getSpilledVariableFieldMapping",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
||||
?: return null
|
||||
|
||||
context.keepReference(rawSpilledVariables)
|
||||
|
||||
val length = rawSpilledVariables.length() / 2
|
||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
||||
|
||||
for (index in 0 until length) {
|
||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
||||
|
||||
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
||||
override fun calcValueName() = variableName
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||
}
|
||||
|
||||
spilledVariables += JavaValue.create(
|
||||
null,
|
||||
valueDescriptor,
|
||||
context.evaluationContext,
|
||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
return spilledVariables
|
||||
}
|
||||
|
||||
private fun ExecutionContext.findClassSafe(className: String): ClassType? {
|
||||
return try {
|
||||
findClass(className) as? ClassType
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncStackTraceContext(
|
||||
val context: ExecutionContext,
|
||||
val method: Method,
|
||||
val debugMetadataKtType: ClassType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -39,6 +39,7 @@ import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import com.sun.jdi.ClassType
|
||||
import javaslang.control.Either
|
||||
import org.jetbrains.kotlin.idea.debugger.AsyncStackTraceContext
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinCoroutinesAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import java.awt.event.MouseEvent
|
||||
@@ -241,7 +242,7 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
val debugMetadataKtType = execContext
|
||||
.findClass("kotlin.coroutines.jvm.internal.DebugMetadataKt") as ClassType
|
||||
val vars = with(KotlinCoroutinesAsyncStackTraceProvider()) {
|
||||
KotlinCoroutinesAsyncStackTraceProvider.AsyncStackTraceContext(
|
||||
AsyncStackTraceContext(
|
||||
execContext,
|
||||
aMethod,
|
||||
debugMetadataKtType
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ import java.awt.event.KeyEvent
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Actually added into ui in [CoroutinesDebugConfigurationExtension.registerCoroutinesPanel]
|
||||
* Actually added into ui in [CoroutineXDebuggerManagerListener.registerCoroutinesPanel]
|
||||
* Some methods are copied from [com.intellij.debugger.ui.impl.ThreadsPanel]
|
||||
*/
|
||||
class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : DebuggerTreePanel(project, stateManager) {
|
||||
|
||||
Reference in New Issue
Block a user