(CoroutineDebugger) Group coroutines by dispatcher

Kotlin Script support added.

 #KT-37917 fixed
This commit is contained in:
Vladimir Ilmov
2020-03-23 19:12:55 +01:00
parent 7aa9b81604
commit 38ec388429
29 changed files with 862 additions and 430 deletions
@@ -10,6 +10,7 @@ import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.*
import org.jetbrains.kotlin.idea.debugger.coroutine.data.PreCoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
@@ -30,26 +31,25 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
else stackFrameList
}
fun lookupForAfterPreflight(
private fun lookupForAfterPreflight(
stackFrame: CoroutinePreflightStackFrame,
suspendContext: SuspendContextImpl
): List<CoroutineStackFrameItem>? {
val resumeWithFrame = stackFrame.resumeWithFrame
val resumeWithFrame = stackFrame.threadPreCoroutineFrames.firstOrNull()
if (threadAndContextSupportsEvaluation(suspendContext, resumeWithFrame)) {
val stackFrames = mutableListOf<CoroutineStackFrameItem>()
stackFrames.addAll(
stackFrame.restoredStackFrame.drop(1).dropLast(1)
) // because first frame has been generated via CoroutinePreflightStackFrame
stackFrames.addAll(stackFrame.coroutineInfoData.stackTrace)
val lastRestoredFrame = stackFrame.restoredStackFrame.last()
// val lastRestoredFrame = stackFrame.coroutineInfoData.stackTrace.first()
stackFrames.addAll(stackFrame.threadPreCoroutineFrames.mapIndexed { index, stackFrameProxyImpl ->
if (index == 0)
PreCoroutineStackFrameItem(stackFrameProxyImpl, lastRestoredFrame) // get location and variables also from restored part
else
// if (index == 0)
// PreCoroutineStackFrameItem(stackFrameProxyImpl, lastRestoredFrame) // get location and variables also from restored part
// else
PreCoroutineStackFrameItem(stackFrameProxyImpl)
})
stackFrames.addAll(stackFrame.coroutineInfoData.creationStackTrace)
return stackFrames
}
return null
@@ -57,19 +57,19 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
fun lookupForResumeContinuation(
frameProxy: StackFrameProxyImpl,
suspendContext: SuspendContextImpl
): List<CoroutineStackFrameItem>? {
suspendContext: SuspendContextImpl,
framesLeft: List<StackFrameProxyImpl>
): CoroutinePreflightStackFrame? {
val location = frameProxy.location()
if (!location.isInKotlinSources())
return null
if (threadAndContextSupportsEvaluation(suspendContext, frameProxy))
return ContinuationHolder.lookupForResumeMethodContinuation(suspendContext, frameProxy)
?.getAsyncStackTraceIfAny()?.stackFrameItems
return ContinuationHolder.lookupContinuation(suspendContext, frameProxy, framesLeft)
return null
}
private fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl) =
suspendContext.supportsEvaluation() && frameProxy.threadProxy().supportsEvaluation()
private fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl?) =
suspendContext.supportsEvaluation() && frameProxy?.threadProxy()?.supportsEvaluation() ?: false
}
@@ -15,13 +15,15 @@ import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.sun.jdi.Location
import org.jetbrains.kotlin.idea.debugger.StackFrameInterceptor
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
class CoroutineStackFrameInterceptor(val project: Project) : StackFrameInterceptor {
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? =
if (debugProcess.xdebugProcess?.session is XDebugSessionImpl &&
frame !is SkipCoroutineStackFrameProxyImpl &&
AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)
)
ContinuationHolder.coroutineExitFrame(frame, debugProcess.debuggerContext.suspendContext as SuspendContextImpl)
ContinuationHolder.coroutineExitFrame(frame, debugProcess.debuggerContext.suspendContext)
else
null
}
@@ -64,12 +64,13 @@ class DebuggerConnection(
connection?.subscribe(XDebuggerManager.TOPIC, this)
}
override fun processStarted(debugProcess: XDebugProcess) =
override fun processStarted(debugProcess: XDebugProcess) {
DebuggerInvocationUtil.swingInvokeLater(project) {
if (debugProcess is JavaDebugProcess) {
disposable = registerXCoroutinesPanel(debugProcess.session)
}
}
}
override fun processStopped(debugProcess: XDebugProcess) {
val rootDisposable = disposable
@@ -33,9 +33,7 @@ class CoroutineDebuggerListener(val project: Project) : DebuggerListener {
val isExternalSystemRunConfiguration = configuration is ExternalSystemRunConfiguration
val isGradleConfiguration = gradleConfiguration(configuration.type.id)
if (runnerSettings == null || isExternalSystemRunConfiguration || isGradleConfiguration) {
log.warn("Coroutine debugger in standalone mode for ${configuration.name} ${configuration.javaClass} / ${params?.javaClass} / ${runnerSettings?.javaClass} (if enabled)")
} else if (runnerSettings is DebuggingRunnerData?)
if (runnerSettings is DebuggingRunnerData && !isExternalSystemRunConfiguration && !isGradleConfiguration)
return DebuggerConnection(project, configuration, params, runnerSettings)
return null
}
@@ -5,17 +5,14 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.command
import com.intellij.debugger.engine.DebugProcess
import com.intellij.debugger.engine.JavaExecutionStack
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.jdi.ClassesByNameProvider
import com.intellij.debugger.jdi.GeneratedLocation
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.util.containers.ContainerUtil
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder.Companion.leftThreadStack
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutinePreflightStackFrame
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.isPreFlight
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
import org.jetbrains.kotlin.idea.debugger.safeLocation
@@ -25,7 +22,6 @@ import org.jetbrains.kotlin.idea.debugger.safeMethod
class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
val debugProcess = suspendContext.debugProcess
private val virtualMachineProxy = debugProcess.virtualMachineProxy
companion object {
const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as creation stacktrace separator in kotlinx.coroutines
@@ -38,45 +34,28 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
val threadReferenceProxyImpl = ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, coroutine.activeThread)
val realFrames = threadReferenceProxyImpl.forceFrames()
var coroutineStackInserted = false
var preflightFound = false
for (runningStackFrameProxy in realFrames) {
if (runningStackFrameProxy.location().isPreFlight()) {
preflightFound = true
continue
}
if (preflightFound) {
val coroutineStack = coroutineStackFrameProvider.lookupForResumeContinuation(runningStackFrameProxy, suspendContext)
if (coroutineStack?.isNotEmpty() == true) {
// clue coroutine stack into the thread's real stack
for (asyncFrame in coroutineStack) {
coroutineStackFrameList.add(
RestoredCoroutineStackFrameItem(
runningStackFrameProxy,
asyncFrame.location,
asyncFrame.spilledVariables
)
)
coroutineStackInserted = true
}
val leftThreadStack = leftThreadStack(runningStackFrameProxy) ?: continue
val coroutineStack =
coroutineStackFrameProvider.lookupForResumeContinuation(runningStackFrameProxy, suspendContext, leftThreadStack) ?: continue
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(coroutineStack.stackFrameProxy))
// clue coroutine stack into the thread's real stack
val stackFrameItems = coroutineStack.coroutineInfoData.stackTrace.map {
RestoredCoroutineStackFrameItem(
runningStackFrameProxy,
it.location,
it.spilledVariables
)
}
preflightFound = false
}
if (!(coroutineStackInserted && isInvokeSuspendNegativeLineMethodFrame(runningStackFrameProxy)))
coroutineStackFrameList.addAll(stackFrameItems)
} else
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy))
coroutineStackInserted = false
}
} else if ((coroutine.isSuspended() || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference)
} else if (coroutine.isSuspended())
coroutineStackFrameList.addAll(coroutine.stackTrace)
coroutineStackFrameList.addAll(coroutine.creationStackTrace)
coroutine.stackFrameList.addAll(coroutineStackFrameList)
return coroutineStackFrameList
}
private fun isInvokeSuspendNegativeLineMethodFrame(frame: StackFrameProxyImpl) =
frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" &&
frame.safeLocation()?.safeMethod()?.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" &&
frame.safeLocation()?.safeLineNumber() ?: 0 < 0
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.debugger.ui.impl.watch.MethodsTracker
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
@@ -17,11 +18,32 @@ import com.intellij.xdebugger.frame.XNamedValue
import com.intellij.xdebugger.frame.XStackFrame
import com.sun.jdi.Location
import com.sun.jdi.ObjectReference
import com.sun.jdi.StackFrame
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
import org.jetbrains.kotlin.idea.debugger.coroutine.coroutineDebuggerTraceEnabled
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
class CreationCoroutineStackFrameItem(
class FirstCreationCoroutineStackFrameItem(
stackTraceElement: StackTraceElement,
location: Location
) : CreationCoroutineStackFrameItem(stackTraceElement, location) {
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
return CreationCoroutineStackFrame(debugProcess, this)
}
}
class CreationCoroutineStackFrame(debugProcess: DebugProcessImpl, item: StackFrameItem) : CoroutineStackFrame(debugProcess, item) {
override fun getCaptionAboveOf() = KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.trace")
override fun hasSeparatorAbove(): Boolean =
true
}
open class CreationCoroutineStackFrameItem(
val stackTraceElement: StackTraceElement,
location: Location
) : CoroutineStackFrameItem(location, emptyList()) {
@@ -97,13 +119,14 @@ class PreCoroutineStackFrameItem(val frame: StackFrameProxyImpl, location: Locat
}
/**
* Can act as a joint frame, take variables form restored frame and information from the original one.
* Can act as a joint frame, take variables from restored frame and information from the original one.
*/
class PreCoroutineStackFrame(val frame: StackFrameProxyImpl, val debugProcess: DebugProcessImpl, item: StackFrameItem) :
CoroutineStackFrame(debugProcess, item) {
override fun computeChildren(node: XCompositeNode) {
debugProcess.invokeInManagerThread {
debugProcess.getPositionManager().createStackFrame(frame, debugProcess, frame.location())
val skipCoroutineFrame = SkipCoroutineStackFrameProxyImpl(frame)
debugProcess.getPositionManager().createStackFrame(skipCoroutineFrame, debugProcess, frame.location())
?.computeChildren(node) // hack but works
}
super.computeChildren(node)
@@ -114,7 +137,7 @@ open class CoroutineStackFrame(debugProcess: DebugProcessImpl, val item: StackFr
StackFrameItem.CapturedStackFrame(debugProcess, item) {
override fun customizePresentation(component: ColoredTextContainer) {
if (coroutineDebuggerTraceEnabled())
component.append("${item.javaClass.simpleName} / ${this.javaClass.simpleName}", SimpleTextAttributes.GRAYED_ATTRIBUTES)
component.append("${item.javaClass.simpleName} / ${this.javaClass.simpleName} ", SimpleTextAttributes.GRAYED_ATTRIBUTES)
super.customizePresentation(component)
}
@@ -7,7 +7,11 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
import com.sun.jdi.ObjectReference
import com.sun.jdi.ThreadReference
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineHolder
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.LocationCache
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.isAbstractCoroutine
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
/**
* Represents state of a coroutine.
@@ -15,23 +19,11 @@ import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineHolder
*/
data class CoroutineInfoData(
val key: CoroutineNameIdState,
val stackTrace: List<CoroutineStackFrameItem>,
val stackTrace: MutableList<CoroutineStackFrameItem>,
val creationStackTrace: List<CreationCoroutineStackFrameItem>,
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
val lastObservedFrameFieldRef: ObjectReference?
val lastObservedFrameFieldRef: ObjectReference? = null
) {
var stackFrameList = mutableListOf<CoroutineStackFrameItem>()
// @TODO for refactoring/removal along with DumpPanel
val stringStackTrace: String by lazy {
buildString {
appendln("\"${key.name}\", state: ${key.state}")
stackTrace.forEach {
appendln("\t$it")
}
}
}
fun isSuspended() = key.state == State.SUSPENDED
fun isCreated() = key.state == State.CREATED
@@ -41,16 +33,79 @@ data class CoroutineInfoData(
fun isRunning() = key.state == State.RUNNING
companion object {
fun suspendedCoroutineInfoData(
holder: CoroutineHolder,
lastObservedFrameFieldRef: ObjectReference
val log by logger
const val DEFAULT_COROUTINE_NAME = "coroutine"
const val DEFAULT_COROUTINE_STATE = "UNKNOWN"
fun lookup(
input: ObjectReference?,
context: DefaultExecutionContext,
stackFrameItems: List<CoroutineStackFrameItem>
): CoroutineInfoData? {
return CoroutineInfoData(holder.info, holder.stackFrameItems, emptyList(), null, lastObservedFrameFieldRef)
val locationCache = LocationCache(context)
val creationStackTrace = mutableListOf<CreationCoroutineStackFrameItem>()
val realState = if (input?.type()?.isAbstractCoroutine() ?: false) {
state(input, context) ?: return null
} else {
val ci = DebugProbesImpl(context).getCoroutineInfo(input, context)
if (ci != null) {
if (ci.creationStackTrace != null)
for (frame in ci.creationStackTrace.mapNotNull { it.stackTraceElement() }) {
creationStackTrace.add(CreationCoroutineStackFrameItem(frame, locationCache.createLocation(frame)))
}
CoroutineNameIdState.instance(ci)
} else {
log.warn("Coroutine information not found, ${input?.type()} is not subtype of AbstractCoroutine as expected.")
CoroutineNameIdState(DEFAULT_COROUTINE_NAME, "-1", State.UNKNOWN, null)
}
}
return CoroutineInfoData(realState, stackFrameItems.toMutableList(), creationStackTrace)
}
fun state(value: ObjectReference?, context: DefaultExecutionContext): CoroutineNameIdState? {
value ?: return null
val reference = JavaLangMirror(context)
val standaloneCoroutine = StandaloneCoroutine(context)
val standAloneCoroutineMirror = standaloneCoroutine.mirror(value, context)
if (standAloneCoroutineMirror?.context is MirrorOfCoroutineContext) {
val id = standAloneCoroutineMirror.context.id
val name = standAloneCoroutineMirror.context.name ?: DEFAULT_COROUTINE_NAME
val toString = reference.string(value, context)
val r = """\w+\{(\w+)\}\@([\w\d]+)""".toRegex()
val matcher = r.toPattern().matcher(toString)
if (matcher.matches()) {
val state = stateOf(matcher.group(1))
val hexAddress = matcher.group(2)
return CoroutineNameIdState(name, id?.toString() ?: hexAddress, state, standAloneCoroutineMirror.context.dispatcher)
}
}
return null
}
private fun stateOf(state: String?): State =
when (state) {
"Active" -> State.RUNNING
"Cancelling" -> State.SUSPENDED_CANCELLING
"Completing" -> State.SUSPENDED_COMPLETING
"Cancelled" -> State.CANCELLED
"Completed" -> State.COMPLETED
"New" -> State.NEW
else -> State.UNKNOWN
}
}
}
data class CoroutineNameIdState(val name: String, val id: String, val state: State)
data class CoroutineNameIdState(val name: String, val id: String, val state: State, val dispatcher: String?) {
companion object {
fun instance(mirror: MirrorOfCoroutineInfo): CoroutineNameIdState =
CoroutineNameIdState(
mirror.context?.name ?: "coroutine",
"${mirror.sequenceNumber}",
State.valueOf(mirror.state ?: "UNKNOWN"),
mirror.context?.dispatcher
)
}
}
enum class State {
RUNNING,
@@ -24,7 +24,7 @@ import javax.swing.Icon
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
lateinit var icon: Icon
override fun getName() = infoData.key.name
override fun getName() = infoData.key?.name
@Throws(EvaluateException::class)
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
@@ -12,103 +12,76 @@ import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.xdebugger.frame.XNamedValue
import com.intellij.xdebugger.frame.XStackFrame
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutine.data.ContinuationValueDescriptorImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutine.data.DefaultCoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutine.standaloneCoroutineDebuggerEnabled
import org.jetbrains.kotlin.idea.debugger.coroutine.coroutineDebuggerTraceEnabled
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugMetadata
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.FieldVariable
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
import org.jetbrains.kotlin.idea.debugger.safeLocation
data class ContinuationHolder(val continuation: ObjectReference, val context: DefaultExecutionContext) {
val log by logger
private val debugMetadata: DebugMetadata? = DebugMetadata.instance(context)
fun getAsyncStackTraceIfAny(): CoroutineHolder? {
fun getCoroutineInfoData(): CoroutineInfoData? {
try {
return collectFrames()
return collectCoroutineInfo()
} catch (e: Exception) {
log.error("Error while looking for variables.", e)
log.error("Error while looking for stack frame.", e)
}
return null
}
private fun collectFrames(): CoroutineHolder? {
private fun collectCoroutineInfo(): CoroutineInfoData? {
val consumer = mutableListOf<CoroutineStackFrameItem>()
var completion = this
val debugMetadataKtType = debugMetadataKtType() ?: return null
while (completion.isBaseContinuationImpl()) {
val coroutineStackFrame = context.debugProcess.invokeInManagerThread {
createLocation(completion, debugMetadataKtType)
completion.createLocation()
}
if (coroutineStackFrame != null) {
consumer.add(coroutineStackFrame)
}
completion = completion.findCompletion() ?: break
}
if (completion.value().type().isAbstractCoroutine())
return CoroutineHolder.lookup(completion.value(), context, consumer)
else {
log.warn("AbstractCoroutine not found, ${completion.value().type()} is not subtype of AbstractCoroutine as expected.")
return CoroutineHolder.lookup(null, context, consumer)
}
return CoroutineInfoData.lookup(completion.value(), context, consumer)
}
private fun createLocation(continuation: ContinuationHolder, debugMetadataKtType: ClassType): DefaultCoroutineStackFrameItem? {
val instance = invokeGetStackTraceElement(continuation, debugMetadataKtType) ?: return null
val className = context.invokeMethodAsString(instance, "getClassName") ?: return null
val methodName = context.invokeMethodAsString(instance, "getMethodName") ?: return null
val lineNumber = context.invokeMethodAsInt(instance, "getLineNumber")?.takeIf {
it >= 0
} ?: return null // skip invokeSuspend:-1
val locationClass = context.findClassSafe(className) ?: return null
val generatedLocation = GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
val spilledVariables = getSpilledVariables(continuation, debugMetadataKtType) ?: emptyList()
private fun createLocation(): DefaultCoroutineStackFrameItem? {
val stackTraceElementMirror = debugMetadata?.getStackTraceElement(continuation, context) ?: return null
val stackTraceElement = stackTraceElementMirror.stackTraceElement()
val locationClass = context.findClassSafe(stackTraceElement.className) ?: return null
val generatedLocation =
GeneratedLocation(context.debugProcess, locationClass, stackTraceElement.methodName, stackTraceElement.lineNumber)
val spilledVariables = getSpilledVariables() ?: emptyList()
return DefaultCoroutineStackFrameItem(generatedLocation, spilledVariables)
}
private fun invokeGetStackTraceElement(continuation: ContinuationHolder, debugMetadataKtType: ClassType): ObjectReference? {
val stackTraceElement =
context.invokeMethodAsObject(debugMetadataKtType, "getStackTraceElement", continuation.value()) ?: return null
stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name } ?: return null
context.keepReference(stackTraceElement)
return stackTraceElement
}
fun getSpilledVariables(): List<XNamedValue>? {
debugMetadataKtType()?.let {
return getSpilledVariables(this, it)
}
return null
}
private fun getSpilledVariables(continuation: ContinuationHolder, debugMetadataKtType: ClassType): List<XNamedValue>? {
val variables: List<JavaValue> = context.debugProcess.invokeInManagerThread {
FieldVariable.extractFromContinuation(context, continuation.value(), debugMetadataKtType).map {
val valueDescriptor = ContinuationValueDescriptorImpl(
context.project,
continuation,
it.fieldName,
it.variableName
)
JavaValue.create(
null,
valueDescriptor,
context.evaluationContext,
context.debugProcess.xdebugProcess!!.nodeManager,
false
)
debugMetadata?.getSpilledVariableFieldMapping(continuation, context)?.mapNotNull {
fieldVariableToNamedValue(it, this)
}
} ?: emptyList()
return variables
}
private fun debugMetadataKtType(): ClassType? {
val debugMetadataKtType = context.findCoroutineMetadataType()
if (debugMetadataKtType == null)
log.warn("Continuation information found but no 'kotlin.coroutines.jvm.internal.DebugMetadataKt' class exists. Please check kotlin-stdlib version.")
return debugMetadataKtType
fun fieldVariableToNamedValue(fieldVariable: FieldVariable, continuation: ContinuationHolder): JavaValue {
val valueDescriptor = ContinuationValueDescriptorImpl(
context.project,
continuation,
fieldVariable.fieldName,
fieldVariable.variableName
)
return JavaValue.create(
null,
valueDescriptor,
context.evaluationContext,
context.debugProcess.xdebugProcess!!.nodeManager,
false
)
}
fun referenceType(): ClassType? =
@@ -132,60 +105,70 @@ data class ContinuationHolder(val continuation: ObjectReference, val context: De
fun isBaseContinuationImpl() =
continuation.type().isBaseContinuationImpl()
companion object {
val log by logger
fun lookupForResumeMethodContinuation(
suspendContext: SuspendContextImpl,
frame: StackFrameProxyImpl
): ContinuationHolder? {
if (frame.location().isPreExitFrame()) {
val context = suspendContext.executionContext() ?: return null
var continuation = frame.completionVariableValue() ?: return null
context.keepReference(continuation)
return ContinuationHolder(continuation, context)
} else
return null
}
fun coroutineExitFrame(
frame: StackFrameProxyImpl,
suspendContext: SuspendContextImpl
suspendContext: SuspendContextImpl?
): XStackFrame? {
suspendContext ?: return null
return suspendContext.invokeInManagerThread {
if (frame.location().isPreFlight()) {
if(standaloneCoroutineDebuggerEnabled())
if (frame.location().isPreFlight() || frame.location().isPreExitFrame()) {
if (coroutineDebuggerTraceEnabled())
log.trace("Entry frame found: ${formatLocation(frame.location())}")
constructPreFlightFrame(frame, suspendContext)
val leftThreadStack = leftThreadStack(frame) ?: return@invokeInManagerThread null
lookupContinuation(suspendContext, frame, leftThreadStack)
} else
null
}
}
fun constructPreFlightFrame(
invokeSuspendFrame: StackFrameProxyImpl,
suspendContext: SuspendContextImpl
): CoroutinePreflightStackFrame? {
var frames = invokeSuspendFrame.threadProxy().frames()
val indexOfCurrentFrame = frames.indexOf(invokeSuspendFrame)
val indexofgetcoroutineSuspended = findGetCoroutineSuspended(frames)
// @TODO if found - skip this thread stack
if (indexOfCurrentFrame > 0 && frames.size > indexOfCurrentFrame && indexofgetcoroutineSuspended < 0) {
val resumeWithFrame = frames[indexOfCurrentFrame + 1] ?: return null
val ch = lookupForResumeMethodContinuation(suspendContext, resumeWithFrame) ?: return null
val coroutineStackTrace = ch.getAsyncStackTraceIfAny() ?: return null
return CoroutinePreflightStackFrame.preflight(
invokeSuspendFrame,
resumeWithFrame,
coroutineStackTrace.stackFrameItems,
frames.drop(indexOfCurrentFrame)
)
}
return null
fun leftThreadStack(frame: StackFrameProxyImpl): List<StackFrameProxyImpl>? {
var frames = frame.threadProxy().frames()
val indexOfCurrentFrame = frames.indexOf(frame)
if (indexOfCurrentFrame >= 0) {
val indexofGetCoroutineSuspended = hasGetCoroutineSuspended(frames)
// @TODO if found - skip this thread stack
if (indexofGetCoroutineSuspended >= 0)
return null
return frames.drop(indexOfCurrentFrame)
} else
return null
}
fun lookupContinuation(
suspendContext: SuspendContextImpl,
previousFrame: StackFrameProxyImpl, // invokeSuspend
framesLeft: List<StackFrameProxyImpl>
): CoroutinePreflightStackFrame? {
val continuation =
if (previousFrame.safeLocation()?.method()?.isSuspendLambda() ?: false ||
previousFrame.safeLocation()?.method()?.isContinuation() ?: false
)
getThisContinuation(previousFrame)
else
null
if (continuation != null) {
val context = suspendContext.executionContext() ?: return null
val coroutineStackTrace = ContinuationHolder(continuation, context).getCoroutineInfoData() ?: return null
return CoroutinePreflightStackFrame.preflight(
previousFrame,
coroutineStackTrace,
framesLeft
)
} else
return null
}
private fun getCompletionContinuation(previousFrame: StackFrameProxyImpl?) =
previousFrame?.completion1VariableValue()
private fun getThisContinuation(previousFrame: StackFrameProxyImpl?): ObjectReference? =
previousFrame?.thisVariableValue()
private fun formatLocation(location: Location): String {
return "${location.method().name()}:${location.lineNumber()}, ${location.method().declaringType()} in ${location.sourceName()}"
}
@@ -198,16 +181,11 @@ data class ContinuationHolder(val continuation: ObjectReference, val context: De
fun lookup(
context: SuspendContextImpl,
initialContinuation: ObjectReference?,
// frame: StackTraceElement,
// threadProxy: ThreadReferenceProxyImpl
): ContinuationHolder? {
var continuation = initialContinuation ?: return null
// val classLine = ClassNameLineNumber(frame.className, frame.lineNumber)
val executionContext = context.executionContext() ?: return null
do {
// val position = getClassAndLineNumber(executionContext, continuation)
// while continuation is BaseContinuationImpl and it's frame equals to the current
continuation = getNextFrame(executionContext, continuation) ?: return null
} while (continuation.type().isBaseContinuationImpl() /* && position != classLine */)
@@ -216,24 +194,7 @@ data class ContinuationHolder(val continuation: ObjectReference, val context: De
else
return null
}
data class ClassNameLineNumber(val className: String?, val lineNumber: Int?)
//
// private fun getClassAndLineNumber(context: ExecutionContext, continuation: ObjectReference): ClassNameLineNumber {
// val objectReference = findStackTraceElement(context, continuation) ?: return ClassNameLineNumber(null, null)
// val classStackTraceElement = context.findClass("java.lang.StackTraceElement") as ClassType
// val getClassName = classStackTraceElement.concreteMethodByName("getClassName", "()Ljava/lang/String;")
// val getLineNumber = classStackTraceElement.concreteMethodByName("getLineNumber", "()I")
// val className = (context.invokeMethod(objectReference, getClassName, emptyList()) as StringReference).value()
// val lineNumber = (context.invokeMethod(objectReference, getLineNumber, emptyList()) as IntegerValue).value()
// return ClassNameLineNumber(className, lineNumber)
// }
// private fun findStackTraceElement(context: ExecutionContext, continuation: ObjectReference): ObjectReference? {
// val classType = continuation.type() as ClassType
// val methodGetStackTraceElement = classType.concreteMethodByName("getStackTraceElement", "()Ljava/lang/StackTraceElement;")
// return context.invokeMethod(continuation, methodGetStackTraceElement, emptyList()) as? ObjectReference
// }
}
}
@@ -36,7 +36,7 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
}
private fun findProvider(executionContext: DefaultExecutionContext) =
CoroutineLibraryAgentProxy.instance(executionContext) ?: CoroutineNoLibraryProxy(executionContext)
CoroutineLibraryAgent2Proxy.instance(executionContext) ?: CoroutineNoLibraryProxy(executionContext)
fun frameBuilder() = CoroutineBuilder(suspendContext)
}
@@ -1,64 +0,0 @@
/*
* Copyright 2010-2020 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.coroutine.proxy
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineNameIdState
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutine.data.State
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.MirrorOfCoroutineContext
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
data class CoroutineHolder(
val value: ObjectReference?,
val info: CoroutineNameIdState,
val stackFrameItems: List<CoroutineStackFrameItem>
) {
companion object {
fun lookup(
value: ObjectReference?,
context: DefaultExecutionContext,
stackFrameItems: List<CoroutineStackFrameItem>
): CoroutineHolder? {
val state = state(value, context) ?: return null
val realState =
if (stackFrameItems.isEmpty()) state.copy(state = State.CREATED) else state.copy(state = State.SUSPENDED)
return CoroutineHolder(value, realState, stackFrameItems)
}
fun state(value: ObjectReference?, context: DefaultExecutionContext): CoroutineNameIdState? {
value ?: return null
val reference = JavaLangMirror(context)
val standAloneCoroutineMirror = reference.standaloneCoroutine.mirror(value, context)
if (standAloneCoroutineMirror?.context is MirrorOfCoroutineContext) {
val id = standAloneCoroutineMirror.context.id
val toString = reference.string(value, context)
val r = """\w+\{(\w+)\}\@([\w\d]+)""".toRegex()
val matcher = r.toPattern().matcher(toString)
if (matcher.matches()) {
val state = stateOf(matcher.group(1))
val hexAddress = matcher.group(2)
return CoroutineNameIdState(standAloneCoroutineMirror.context.name, id?.toString() ?: hexAddress, state)
}
}
return null
}
private fun stateOf(state: String?): State =
when (state) {
"Active" -> State.RUNNING
"Cancelling" -> State.SUSPENDED_CANCELLING
"Completing" -> State.SUSPENDED_COMPLETING
"Cancelled" -> State.CANCELLED
"Completed" -> State.COMPLETED
"New" -> State.NEW
else -> State.UNKNOWN
}
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2020 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.coroutine.proxy
import com.intellij.debugger.engine.DebugProcess
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.jdi.ClassesByNameProvider
import com.intellij.debugger.jdi.GeneratedLocation
import com.intellij.util.containers.ContainerUtil
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineContext
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.MirrorOfCoroutineInfo
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecutionContext) :
CoroutineInfoProvider {
val log by logger
private val debugProbesImpl = DebugProbesImpl(executionContext)
private val locationCache = LocationCache(executionContext)
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
val result = debugProbesImpl.dumpCoroutinesInfo(executionContext)
return result.mapNotNull { mapToCoroutineInfoData(it) }
}
fun mapToCoroutineInfoData(mirror: MirrorOfCoroutineInfo): CoroutineInfoData? {
val cnis = CoroutineNameIdState.instance(mirror)
val stackTrace = mirror.enchancedStackTrace?.mapNotNull { it.stackTraceElement() } ?: emptyList()
var stackFrames = findStackFrames(stackTrace)
return CoroutineInfoData(
cnis,
stackFrames.restoredStackFrames.toMutableList(),
stackFrames.creationStackFrames,
mirror.lastObservedThread,
mirror.lastObservedFrame
)
}
fun isInstalled(): Boolean {
try {
return debugProbesImpl.isInstalledValue ?: false
} catch (e: Exception) {
log.error("Exception happened while checking agent status.", e)
return false
}
}
private fun findStackFrames(frames: List<StackTraceElement>): CoroutineStackFrames {
val index = frames.indexOfFirst { it.isCreationSeparatorFrame() }
return CoroutineStackFrames(frames.take(index).map {
SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it))
}, frames.subList(index + 1, frames.size).map {
CreationCoroutineStackFrameItem(it, locationCache.createLocation(it))
})
}
data class CoroutineStackFrames(
val restoredStackFrames: List<SuspendCoroutineStackFrameItem>,
val creationStackFrames: List<CreationCoroutineStackFrameItem>
)
companion object {
fun instance(executionContext: DefaultExecutionContext): CoroutineLibraryAgent2Proxy? {
val agentProxy = CoroutineLibraryAgent2Proxy(executionContext)
if (agentProxy.isInstalled())
return agentProxy
else
return null
}
}
}
@@ -13,6 +13,8 @@ import com.intellij.util.containers.ContainerUtil
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineContext
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineInfo
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
@@ -83,11 +85,11 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva
val creationStackTraceFrameItems = creationStackTrace.map {
CreationCoroutineStackFrameItem(it, createLocation(it))
}
val key = CoroutineNameIdState(name,"", State.valueOf(state))
val key = CoroutineNameIdState(name, "", State.valueOf(state), "")
return CoroutineInfoData(
key,
coroutineStackTraceFrameItems,
coroutineStackTraceFrameItems.toMutableList(),
creationStackTraceFrameItems,
thread,
lastObservedFrameFieldRef
@@ -25,7 +25,6 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
"CANCELLABLE_CONTINUATION" -> cancellableContinuation(resultList)
else -> dispatchedContinuation(resultList)
}
} else
log.warn("Remote JVM doesn't support canGetInstanceInfo capability (perhaps JDK-8197943).")
return resultList
@@ -52,9 +51,7 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
): CoroutineInfoData? {
val mirror = ccMirrorProvider.mirror(dispatchedContinuation, executionContext) ?: return null
val continuation = mirror.delegate?.continuation ?: return null
val ch = ContinuationHolder(continuation, executionContext)
val coroutineWithRestoredStack = ch.getAsyncStackTraceIfAny() ?: return null
return CoroutineInfoData.suspendedCoroutineInfoData(coroutineWithRestoredStack, continuation)
return ContinuationHolder(continuation, executionContext).getCoroutineInfoData()
}
private fun dispatchedContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
@@ -74,11 +71,8 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? {
debugMetadataKtType ?: return null
val initialContinuation = dispatchedContinuation.getValue(continuation) as ObjectReference
val ch = ContinuationHolder(initialContinuation, executionContext)
val coroutineWithRestoredStack = ch.getAsyncStackTraceIfAny() ?: return null
return CoroutineInfoData.suspendedCoroutineInfoData(coroutineWithRestoredStack, initialContinuation)
return ContinuationHolder(initialContinuation, executionContext).getCoroutineInfoData()
}
}
fun maxCoroutines() = Registry.intValue("kotlin.debugger.coroutines.max", 1000).toLong()
@@ -8,13 +8,21 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.JVMStackFrameInfoProvider
import com.intellij.debugger.jdi.GeneratedLocation
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.debugger.ui.impl.watch.MethodsTracker
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.xdebugger.frame.XCompositeNode
import com.intellij.xdebugger.frame.XValueChildrenList
import com.sun.jdi.Location
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CreationCoroutineStackFrame
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod
import org.jetbrains.kotlin.idea.debugger.stackFrame.KotlinStackFrame
/**
@@ -25,16 +33,14 @@ import org.jetbrains.kotlin.idea.debugger.stackFrame.KotlinStackFrame
*/
class CoroutinePreflightStackFrame(
val invokeSuspendFrame: StackFrameProxyImpl,
val resumeWithFrame: StackFrameProxyImpl,
val restoredStackFrame: List<CoroutineStackFrameItem>,
val coroutineInfoData: CoroutineInfoData,
val stackFrameDescriptorImpl: StackFrameDescriptorImpl,
val threadPreCoroutineFrames: List<StackFrameProxyImpl>
val threadPreCoroutineFrames: List<StackFrameProxyImpl>,
) : KotlinStackFrame(stackFrameDescriptorImpl), JVMStackFrameInfoProvider {
override fun computeChildren(node: XCompositeNode) {
val childrenList = XValueChildrenList()
val firstRestoredCoroutineStackFrameItem = restoredStackFrame.firstOrNull() ?: return
val firstRestoredCoroutineStackFrameItem = coroutineInfoData.stackTrace.firstOrNull() ?: return
firstRestoredCoroutineStackFrameItem.spilledVariables.forEach {
childrenList.add(it)
}
@@ -51,15 +57,45 @@ class CoroutinePreflightStackFrame(
companion object {
fun preflight(
invokeSuspendFrame: StackFrameProxyImpl,
resumeWithFrame: StackFrameProxyImpl,
restoredStackFrame: List<CoroutineStackFrameItem>,
coroutineInfoData: CoroutineInfoData,
originalFrames: List<StackFrameProxyImpl>
): CoroutinePreflightStackFrame? {
val topRestoredFrame = restoredStackFrame.firstOrNull() ?: return null
val descriptor = StackFrameDescriptorImpl(
LocationStackFrameProxyImpl(topRestoredFrame.location, invokeSuspendFrame), MethodsTracker()
val descriptor = createFirstRestoredFrame(invokeSuspendFrame, coroutineInfoData)
return CoroutinePreflightStackFrame(
coroutineInfoData,
descriptor,
originalFrames.filter { ! isInvokeSuspendNegativeLineMethodFrame(it) }
)
return CoroutinePreflightStackFrame(invokeSuspendFrame, resumeWithFrame, restoredStackFrame, descriptor, originalFrames)
}
fun createFirstRestoredFrame(
invokeSuspendFrame: StackFrameProxyImpl,
coroutineInfoData: CoroutineInfoData
): StackFrameDescriptorImpl {
if (coroutineInfoData.stackTrace.size >= 2) {
// assume firstFrame is invokeSuspend and second is resumeWith
val fisrtRestoredFrame = coroutineInfoData.stackTrace.removeAt(0)
val secondRestoredFrame = coroutineInfoData.stackTrace.removeAt(0)
println(formatLocation(invokeSuspendFrame.location()))
println(formatLocation(fisrtRestoredFrame.location))
println(formatLocation(secondRestoredFrame.location))
val descriptor = StackFrameDescriptorImpl(
LocationStackFrameProxyImpl(secondRestoredFrame.location, invokeSuspendFrame), MethodsTracker()
)
return descriptor
} else {
return StackFrameDescriptorImpl(invokeSuspendFrame, MethodsTracker())
}
}
private fun formatLocation(location: Location): String {
return "${location.method().name()}:${location.lineNumber()}, ${location.method().declaringType()}"
}
private fun isInvokeSuspendNegativeLineMethodFrame(frame: StackFrameProxyImpl) =
frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" &&
frame.safeLocation()?.safeMethod()?.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" &&
frame.safeLocation()?.safeLineNumber() ?: 0 < 0
}
}
@@ -64,22 +64,25 @@ fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
fun StackFrameProxyImpl.completionVariableValue(): ObjectReference? =
variableValue("completion")
fun StackFrameProxyImpl.completion1VariableValue(): ObjectReference? =
variableValue("completion")
fun StackFrameProxyImpl.thisVariableValue(): ObjectReference? =
this.thisObject()
private fun Method.isGetCOROUTINE_SUSPENDED() =
signature() == "()Ljava/lang/Object;" && name() == "getCOROUTINE_SUSPENDED" && declaringType().name() == "kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsKt"
fun DefaultExecutionContext.findCoroutineMetadataType() =
debugProcess.invokeInManagerThread { findClassSafe("kotlin.coroutines.jvm.internal.DebugMetadataKt") }
fun DefaultExecutionContext.findCoroutineAnnotationMetadataType() =
debugProcess.invokeInManagerThread { findClassSafe("kotlin.coroutines.jvm.internal.DebugMetadata") as InterfaceType? }
fun DefaultExecutionContext.findDispatchedContinuationReferenceType(): List<ReferenceType>? =
vm.classesByName("kotlinx.coroutines.DispatchedContinuation")
fun DefaultExecutionContext.findCancellableContinuationImplReferenceType(): List<ReferenceType>? =
vm.classesByName("kotlinx.coroutines.CancellableContinuationImpl")
fun findGetCoroutineSuspended(frames: List<StackFrameProxyImpl>) =
fun hasGetCoroutineSuspended(frames: List<StackFrameProxyImpl>) =
frames.indexOfFirst { it.safeLocation()?.safeMethod()?.isGetCOROUTINE_SUSPENDED() == true }
fun StackTraceElement.isCreationSeparatorFrame() =
@@ -92,7 +95,7 @@ fun Location.findPosition(project: Project) =
getPosition(project, declaringType().name(), lineNumber())
fun ClassType.completionField() =
fieldByName("completion") as Field?
fieldByName("completion")
private fun getPosition(project: Project, className: String, lineNumber: Int): XSourcePosition? {
val psiFacade = JavaPsiFacade.getInstance(project)
@@ -1,44 +0,0 @@
/*
* Copyright 2010-2020 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.coroutine.proxy
import com.sun.jdi.ArrayReference
import com.sun.jdi.ClassType
import com.sun.jdi.ObjectReference
import com.sun.jdi.StringReference
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
data class FieldVariable(val fieldName: String, val variableName: String) {
companion object {
fun extractFromContinuation(context: DefaultExecutionContext, continuation: ObjectReference, debugMetadataKtType: ClassType?) : List<FieldVariable> {
val metadataType = debugMetadataKtType ?: context.findCoroutineMetadataType() ?: return emptyList()
val rawSpilledVariables =
context.invokeMethodAsArray(
metadataType,
"getSpilledVariableFieldMapping",
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;",
continuation
) ?: return emptyList()
context.keepReference(rawSpilledVariables)
val length = rawSpilledVariables.length() / 2
val fieldVariables = ArrayList<FieldVariable>()
for (index in 0 until length) {
fieldVariables.add(getFieldVariableName(rawSpilledVariables, index) ?: continue)
}
return fieldVariables
}
private fun getFieldVariableName(rawSpilledVariables: ArrayReference, index: Int): FieldVariable? {
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: return null
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: return null
return FieldVariable(fieldName, variableName)
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2020 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.coroutine.proxy
import com.intellij.debugger.engine.DebugProcess
import com.intellij.debugger.jdi.ClassesByNameProvider
import com.intellij.debugger.jdi.GeneratedLocation
import com.intellij.util.containers.ContainerUtil
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.Location
import com.sun.jdi.ReferenceType
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class LocationCache(val context: DefaultExecutionContext) {
private val classesByName = ClassesByNameProvider.createCache(context.vm.allClasses())
fun createLocation(stackTraceElement: StackTraceElement): Location = findLocation(
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
stackTraceElement.methodName,
stackTraceElement.lineNumber
)
private fun findLocation(
type: ReferenceType?,
methodName: String,
line: Int
): Location {
if (type != null && line >= 0) {
try {
val location = type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line).stream()
.filter { l: Location -> l.method().name() == methodName }
.findFirst().orElse(null)
if (location != null) {
return location
}
} catch (ignored: AbsentInformationException) {
}
}
return GeneratedLocation(context.debugProcess, type, methodName, line)
}
}
@@ -6,11 +6,17 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.sun.jdi.Location
import com.sun.jdi.StackFrame
class LocationStackFrameProxyImpl(val location: Location, frame: StackFrameProxyImpl) :
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom) {
override fun location(): Location {
return location
}
}
}
class SkipCoroutineStackFrameProxyImpl(frame: StackFrameProxyImpl) :
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom)
@@ -8,41 +8,45 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
import com.sun.jdi.Method
import com.sun.jdi.ObjectReference
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
import java.lang.StackTraceElement
class CoroutineContext(context: DefaultExecutionContext) :
BaseMirror<MirrorOfCoroutineContext>("kotlin.coroutines.CoroutineContext", context) {
val coroutineNameRef = CoroutineName(context)
val coroutineIdRef = CoroutineId(context)
val jobRef = Job(context)
val getContextElement: Method = makeMethod("get")
val dispatcherRef = CoroutineDispatcher(context)
val getContextElement = makeMethod("get")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineContext? {
val coroutineName = getElementValue(value, context, coroutineNameRef) ?: "coroutine"
val coroutineName = getElementValue(value, context, coroutineNameRef)
val coroutineId = getElementValue(value, context, coroutineIdRef)
val job = getElementValue(value, context, jobRef)
return MirrorOfCoroutineContext(value, coroutineName, coroutineId, job)
val dispatcher = getElementValue(value, context, dispatcherRef)
return MirrorOfCoroutineContext(value, coroutineName, coroutineId, dispatcher, job)
}
fun <T> getElementValue(value: ObjectReference, context: DefaultExecutionContext, keyProvider: ContextKey<T>): T? {
val elementValue = objectValue(value, getContextElement, context, keyProvider.key()) ?: return null
val elementValue = objectValue(value, getContextElement, context, keyProvider.key() ?: return null) ?: return null
return keyProvider.mirror(elementValue, context)
}
}
data class MirrorOfCoroutineContext(
val that: ObjectReference,
val name: String,
val name: String?,
val id: Long?,
val dispatcher: String?,
val job: ObjectReference?
)
abstract class ContextKey<T>(name: String, context: DefaultExecutionContext) : BaseMirror<T>(name, context) {
abstract fun key() : ObjectReference
abstract fun key() : ObjectReference?
}
class CoroutineName(context: DefaultExecutionContext) : ContextKey<String>("kotlinx.coroutines.CoroutineName", context) {
val key = staticObjectValue("Key")
val getNameRef: Method = makeMethod("getName")
val getNameRef = makeMethod("getName")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): String? {
return stringValue(value, getNameRef, context)
@@ -53,7 +57,7 @@ class CoroutineName(context: DefaultExecutionContext) : ContextKey<String>("kotl
class CoroutineId(context: DefaultExecutionContext) : ContextKey<Long>("kotlinx.coroutines.CoroutineId", context) {
val key = staticObjectValue("Key")
val getIdRef: Method = makeMethod("getId")
val getIdRef = makeMethod("getId")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): Long? {
return longValue(value, getIdRef, context)
@@ -70,4 +74,16 @@ class Job(context: DefaultExecutionContext) : ContextKey<ObjectReference>("kotli
}
override fun key() = key
}
}
class CoroutineDispatcher(context: DefaultExecutionContext) : ContextKey<String>("kotlinx.coroutines.CoroutineDispatcher", context) {
val key = staticObjectValue("Key")
val jlm = JavaLangMirror(context)
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): String? {
return jlm.string(value, context)
}
override fun key() = key
}
@@ -0,0 +1,192 @@
/*
* Copyright 2010-2020 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.coroutine.proxy.mirror
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class DebugProbesImpl(context: DefaultExecutionContext) :
BaseMirror<MirrorOfDebugProbesImpl>("kotlinx.coroutines.debug.internal.DebugProbesImpl", context) {
val javaLangListMirror = JavaUtilList(context)
val stackTraceElement = StackTraceElement(context)
val coroutineInfo = CoroutineInfo(this, context)
val instance = staticObjectValue("INSTANCE")
val isInstalledMethod = makeMethod("isInstalled\$kotlinx_coroutines_debug", "()Z")
val isInstalledValue = booleanValue(instance, isInstalledMethod, context)
val enhanceStackTraceWithThreadDumpMethod = makeMethod("enhanceStackTraceWithThreadDump")
val dumpMethod = makeMethod("dumpCoroutinesInfo", "()Ljava/util/List;")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? {
return MirrorOfDebugProbesImpl(value, instance, isInstalledValue)
}
fun enchanceStackTraceWithThreadDump(
context: DefaultExecutionContext,
coroutineInfo: ObjectReference,
lastObservedStackTrace: ObjectReference
): List<MirrorOfStackTraceElement>? {
val listReference =
staticMethodValue(instance, enhanceStackTraceWithThreadDumpMethod, context, coroutineInfo, lastObservedStackTrace)
val list = javaLangListMirror.mirror(listReference, context) ?: return null
return list.values.mapNotNull { stackTraceElement.mirror(it, context) }
}
fun dumpCoroutinesInfo(context: DefaultExecutionContext): List<MirrorOfCoroutineInfo> {
instance ?: return emptyList()
val coroutinesInfoReference = objectValue(instance, dumpMethod, context)
val referenceList = javaLangListMirror.mirror(coroutinesInfoReference, context) ?: return emptyList()
return referenceList.values.mapNotNull { coroutineInfo.mirror(it, context) }
}
fun getCoroutineInfo(input: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? {
// kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner
val delegate = input?.referenceType()?.fieldByName("info") ?: return null
val coroutine = input.getValue(delegate) as? ObjectReference
return coroutineInfo.mirror(coroutine, context)
}
}
data class MirrorOfDebugProbesImpl(val that: ObjectReference, val instance: ObjectReference?, val isInstalled: Boolean?)
class CoroutineInfo(val debugProbesImplMirror: DebugProbesImpl, context: DefaultExecutionContext) :
BaseMirror<MirrorOfCoroutineInfo>("kotlinx.coroutines.debug.CoroutineInfo", context) {
val javaLangMirror = JavaLangMirror(context)
val javaLangListMirror = JavaUtilList(context)
private val coroutineContextMirror = CoroutineContext(context)
private val coroutineStackFrameMirror = CoroutineStackFrame(context)
private val stackTraceElement = StackTraceElement(context)
private val contextFieldRef = makeField("context")
private val creationStackBottom = makeField("creationStackBottom")
private val sequenceNumberField = makeField("sequenceNumber")
private val creationStackTraceMethod = makeMethod("getCreationStackTrace")
private val stateMethod = makeMethod("getState")
private val lastObservedStackTraceMethod = makeMethod("lastObservedStackTrace")
private val lastObservedFrameField = makeField("lastObservedFrame")
private val lastObservedThreadField = makeField("lastObservedThread")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineInfo {
val state = objectValue(value, stateMethod, context)?.let {
stringValue(it, javaLangMirror.toString, context)
}
val coroutineContext = coroutineContextMirror.mirror(objectValue(value, contextFieldRef), context)
val creationStackBottomObjectReference = objectValue(value, creationStackBottom)
val creationStackBottom = coroutineStackFrameMirror.mirror(creationStackBottomObjectReference, context)
val sequenceNumber = longValue(value, sequenceNumberField)
val creationStackTraceList = objectValue(value, creationStackTraceMethod, context)
val creationStackTraceMirror = javaLangListMirror.mirror(creationStackTraceList, context)
val creationStackTrace = creationStackTraceMirror?.values?.mapNotNull { stackTraceElement.mirror(it, context) }
val lastObservedStackTrace = objectValue(value, lastObservedStackTraceMethod, context)
val enchancedList =
if (lastObservedStackTrace != null)
debugProbesImplMirror.enchanceStackTraceWithThreadDump(context, value, lastObservedStackTrace)
else emptyList()
val lastObservedThread = threadValue(value, lastObservedThreadField)
val lastObservedFrame = threadValue(value, lastObservedFrameField)
return MirrorOfCoroutineInfo(
value,
coroutineContext,
creationStackBottom,
sequenceNumber,
enchancedList,
creationStackTrace,
state,
lastObservedThread,
lastObservedFrame
)
}
}
data class MirrorOfCoroutineInfo(
val that: ObjectReference,
val context: MirrorOfCoroutineContext?,
val creationStackBottom: MirrorOfCoroutineStackFrame?,
val sequenceNumber: Long?,
val enchancedStackTrace: List<MirrorOfStackTraceElement>?,
val creationStackTrace: List<MirrorOfStackTraceElement>?,
val state: String?,
val lastObservedThread: ThreadReference?,
val lastObservedFrame: ObjectReference?
)
class CoroutineStackFrame(context: DefaultExecutionContext) :
BaseMirror<MirrorOfCoroutineStackFrame>("kotlin.coroutines.jvm.internal.CoroutineStackFrame", context) {
private val stackTraceElementMirror = StackTraceElement(context)
private val callerFrameMethod = makeMethod("getCallerFrame")
private val getStackTraceElementMethod = makeMethod("getStackTraceElement")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineStackFrame? {
val objectReference = objectValue(value, callerFrameMethod, context)
val callerFrame = if (objectReference is ObjectReference)
this.mirror(objectReference, context) else null
val stackTraceElementReference = objectValue(value, getStackTraceElementMethod, context)
val stackTraceElement =
if (stackTraceElementReference is ObjectReference) stackTraceElementMirror.mirror(stackTraceElementReference, context) else null
return MirrorOfCoroutineStackFrame(value, callerFrame, stackTraceElement)
}
}
data class MirrorOfCoroutineStackFrame(
val that: ObjectReference,
val callerFrame: MirrorOfCoroutineStackFrame?,
val stackTraceElement: MirrorOfStackTraceElement?
)
class StackTraceElement(context: DefaultExecutionContext) :
BaseMirror<MirrorOfStackTraceElement>("java.lang.StackTraceElement", context) {
private val declaringClassObjectField = makeField("declaringClass")
private val moduleNameField = makeField("moduleName")
private val moduleVersionField = makeField("moduleVersion")
private val declaringClassField = makeField("declaringClass")
private val methodNameField = makeField("methodName")
private val fileNameField = makeField("fileName")
private val lineNumberField = makeField("lineNumber")
private val formatField = makeField("format")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStackTraceElement? {
val declaringClassObject = objectValue(value, declaringClassObjectField)
val moduleName = stringValue(value, moduleNameField)
val moduleVersion = stringValue(value, moduleVersionField)
val declaringClass = stringValue(value, declaringClassField)
val methodName = stringValue(value, methodNameField)
val fileName = stringValue(value, fileNameField)
val lineNumber = intValue(value, lineNumberField)
val format = byteValue(value, formatField)
return MirrorOfStackTraceElement(
value,
declaringClassObject,
moduleName,
moduleVersion,
declaringClass,
methodName,
fileName,
lineNumber,
format
)
}
}
data class MirrorOfStackTraceElement(
val that: ObjectReference,
val declaringClassObject: ObjectReference?,
val moduleName: String?,
val moduleVersion: String?,
val declaringClass: String?,
val methodName: String?,
val fileName: String?,
val lineNumber: Int?,
val format: Byte?
) {
fun stackTraceElement() =
java.lang.StackTraceElement(
declaringClass,
methodName,
fileName,
lineNumber ?: -1
)
}
@@ -13,50 +13,90 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
abstract class BaseMirror<T>(val name: String, context: DefaultExecutionContext) {
val log by logger
protected val cls = context.findClass(name) ?: throw IllegalStateException("Can't find class ${name} in remote jvm.")
protected val cls = context.findClassSafe(name)
fun makeField(fieldName: String): Field =
cls.fieldByName(fieldName) // childContinuation
fun makeField(fieldName: String): Field? =
cls?.let { it.fieldByName(fieldName) }
fun makeMethod(methodName: String): Method =
cls.methodsByName(methodName).single()
fun makeMethod(methodName: String): Method? =
cls?.let { it.methodsByName(methodName).single() }
fun makeMethod(methodName: String, signature: String): Method? =
cls?.let { it.methodsByName(methodName, signature).single() }
fun isCompatible(value: ObjectReference) =
value.referenceType().isSubTypeOrSame(name)
fun mirror(value: ObjectReference, context: DefaultExecutionContext): T? {
fun mirror(value: ObjectReference?, context: DefaultExecutionContext): T? {
value ?: return null
if (!isCompatible(value)) {
log.warn("Value ${value.referenceType()} is not compatible with $name.")
log.trace("Value ${value.referenceType()} is not compatible with $name.")
return null
} else
return fetchMirror(value, context)
}
fun staticObjectValue(fieldName: String): ObjectReference {
fun staticObjectValue(fieldName: String): ObjectReference? {
val keyFieldRef = makeField(fieldName)
return cls.getValue(keyFieldRef) as ObjectReference
return cls?.let { it.getValue(keyFieldRef) as? ObjectReference }
}
fun stringValue(value: ObjectReference, field: Field) =
(value.getValue(field) as StringReference).value()
fun staticMethodValue(instance: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value?) =
instance?.let {
method?.let { m ->
context.invokeMethod(it, m, values.asList()) as? ObjectReference
}
}
fun stringValue(value: ObjectReference, method: Method, context: DefaultExecutionContext) =
(context.invokeMethod(value, method, emptyList()) as StringReference).value()
fun staticMethodValue(method: Method?, context: DefaultExecutionContext, vararg values: Value?) =
cls?.let {
method?.let {
context.invokeMethodSafe(cls, method, values.asList()) as? ObjectReference
}
}
fun objectValue(value: ObjectReference, method: Method, context: DefaultExecutionContext, vararg values: Value) =
context.invokeMethodAsObject(value, method, *values)
fun stringValue(value: ObjectReference, field: Field?) =
(value.getValue(field) as? StringReference)?.value()
fun longValue(value: ObjectReference, method: Method, context: DefaultExecutionContext, vararg values: Value) =
(context.invokeMethodAsObject(value, method, *values) as LongValue).longValue()
fun byteValue(value: ObjectReference, field: Field?) =
(value.getValue(field) as? ByteValue)?.value()
fun objectValue(value: ObjectReference, field: Field) =
value.getValue(field) as ObjectReference
fun threadValue(value: ObjectReference, field: Field?) =
value.getValue(field) as? ThreadReference
fun intValue(value: ObjectReference, field: Field) =
(value.getValue(field) as IntegerValue).intValue()
fun stringValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext) =
method?.let { (context.invokeMethod(value, it, emptyList()) as? StringReference)?.value() }
fun longValue(value: ObjectReference, field: Field) =
(value.getValue(field) as LongValue).longValue()
fun objectValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
value?.let {
method?.let {
context.invokeMethodAsObject(value, method, *values)
}
}
fun longValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
method?.let { (context.invokeMethod(value, it, values.asList()) as? LongValue)?.longValue() }
fun intValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
method?.let { (context.invokeMethod(value, it, values.asList()) as? IntegerValue)?.intValue() }
fun booleanValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value): Boolean? {
value ?: return null
method ?: return null
return (context.invokeMethod(value, method, values.asList()) as? BooleanValue)?.booleanValue()
}
fun objectValue(value: ObjectReference, field: Field?) =
field?.let { value.getValue(it) as ObjectReference? }
fun intValue(value: ObjectReference, field: Field?) =
field?.let { (value.getValue(it) as? IntegerValue)?.intValue() }
fun longValue(value: ObjectReference, field: Field?) =
field?.let { (value.getValue(it) as? LongValue)?.longValue() }
fun booleanValue(value: ObjectReference?, field: Field?) =
field?.let { (value?.getValue(field) as? BooleanValue)?.booleanValue() }
protected abstract fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): T?
}
@@ -65,8 +105,8 @@ class StandaloneCoroutine(context: DefaultExecutionContext) :
BaseMirror<MirrorOfStandaloneCoroutine>("kotlinx.coroutines.StandaloneCoroutine", context) {
private val coroutineContextMirror = CoroutineContext(context)
private val childContinuationMirror = ChildContinuation(context)
private val stateFieldRef: Field = makeField("_state") // childContinuation
private val contextFieldRef: Field = makeField("context")
private val stateFieldRef = makeField("_state") // childContinuation
private val contextFieldRef = makeField("context")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStandaloneCoroutine {
val state = objectValue(value, stateFieldRef)
@@ -76,6 +116,15 @@ class StandaloneCoroutine(context: DefaultExecutionContext) :
return MirrorOfStandaloneCoroutine(value, childcontinuation, coroutineContext)
}
companion object {
fun instance(context: DefaultExecutionContext): StandaloneCoroutine? {
val sc = StandaloneCoroutine(context)
if (sc.cls == null)
return null
else
return sc
}
}
}
data class MirrorOfStandaloneCoroutine(
@@ -87,7 +136,7 @@ data class MirrorOfStandaloneCoroutine(
class ChildContinuation(context: DefaultExecutionContext) :
BaseMirror<MirrorOfChildContinuation>("kotlinx.coroutines.ChildContinuation", context) {
private val childContinuationMirror = CancellableContinuationImpl(context)
private val childFieldRef: Field = makeField("child") // cancellableContinuationImpl
private val childFieldRef = makeField("child") // cancellableContinuationImpl
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfChildContinuation? {
val child = objectValue(value, childFieldRef)
@@ -104,11 +153,11 @@ class CancellableContinuationImpl(context: DefaultExecutionContext) :
BaseMirror<MirrorOfCancellableContinuationImpl>("kotlinx.coroutines.CancellableContinuationImpl", context) {
private val coroutineContextMirror = CoroutineContext(context)
private val dispatchedContinuationtMirror = DispatchedContinuation(context)
private val decisionFieldRef: Field = makeField("_decision")
private val delegateFieldRef: Field = makeField("delegate") // DispatchedContinuation
private val resumeModeFieldRef: Field = makeField("resumeMode")
private val submissionTimeFieldRef: Field = makeField("submissionTime")
private val contextFieldRef: Field = makeField("context")
private val decisionFieldRef = makeField("_decision")
private val delegateFieldRef = makeField("delegate") // DispatchedContinuation
private val resumeModeFieldRef = makeField("resumeMode")
private val submissionTimeFieldRef = makeField("submissionTime")
private val contextFieldRef = makeField("context")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCancellableContinuationImpl? {
val decision = intValue(value, decisionFieldRef)
@@ -123,16 +172,16 @@ class CancellableContinuationImpl(context: DefaultExecutionContext) :
data class MirrorOfCancellableContinuationImpl(
val that: ObjectReference,
val decision: Int,
val decision: Int?,
val delegate: MirrorOfDispatchedContinuation?,
val resumeMode: Int,
val submissionTyme: Long,
val resumeMode: Int?,
val submissionTyme: Long?,
val jobContext: MirrorOfCoroutineContext?
)
class DispatchedContinuation(context: DefaultExecutionContext) :
BaseMirror<MirrorOfDispatchedContinuation>("kotlinx.coroutines.DispatchedContinuation", context) {
private val decisionFieldRef: Field = makeField("continuation")
private val decisionFieldRef = makeField("continuation")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDispatchedContinuation? {
val continuation = objectValue(value, decisionFieldRef)
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2020 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.coroutine.proxy.mirror
import com.sun.jdi.ArrayReference
import com.sun.jdi.ClassType
import com.sun.jdi.ObjectReference
import com.sun.jdi.StringReference
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.isBaseContinuationImpl
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class DebugMetadata(context: DefaultExecutionContext) :
BaseMirror<MirrorOfDebugProbesImpl>("kotlin.coroutines.jvm.internal.DebugMetadataKt", context) {
val getStackTraceElementMethod = makeMethod("getStackTraceElement")
val getSpilledVariableFieldMappingMethod = makeMethod("getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;")
val stackTraceElement = StackTraceElement(context)
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? {
return null
}
fun getStackTraceElement(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStackTraceElement? {
if (value.referenceType().isBaseContinuationImpl()) {
val stackTraceObjectReference = staticMethodValue(getStackTraceElementMethod, context, value) ?: return null
return stackTraceElement.mirror(stackTraceObjectReference, context)
} else
return null
}
fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext): List<FieldVariable> {
val getSpilledVariableFieldMappingReference = staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference ?: return emptyList()
val length = getSpilledVariableFieldMappingReference.length() / 2
val fieldVariables = ArrayList<FieldVariable>()
for (index in 0 until length) {
fieldVariables.add(getFieldVariableName(getSpilledVariableFieldMappingReference, index) ?: continue)
}
return fieldVariables
}
private fun getFieldVariableName(rawSpilledVariables: ArrayReference, index: Int): FieldVariable? {
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: return null
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: return null
return FieldVariable(fieldName, variableName)
}
companion object {
fun instance(context: DefaultExecutionContext) =
try {
DebugMetadata(context)
} catch (e : IllegalStateException) {
null
}
}
}
data class FieldVariable(val fieldName: String, val variableName: String)
@@ -28,9 +28,6 @@ class JavaLangMirror(context: DefaultExecutionContext) {
// java.lang.Class
val classType = context.findClass("java.lang.Class") as ClassType
val standaloneCoroutine = StandaloneCoroutine(context)
fun string(state: ObjectReference, context: DefaultExecutionContext): String =
(context.invokeMethod(state, toString, emptyList()) as StringReference).value()
@@ -63,3 +60,22 @@ class JavaLangMirror(context: DefaultExecutionContext) {
private fun fetchClassName(instance: ObjectReference) =
(instance.getValue(declaringClassFieldRef) as? StringReference)?.value() ?: ""
}
class JavaUtilList(context: DefaultExecutionContext) :
BaseMirror<MirrorOfJavaLangList>("java.util.List", context) {
val sizeMethod = makeMethod("size")
val getMethod = makeMethod("get")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfJavaLangList? {
val list = mutableListOf<ObjectReference>()
val size = intValue(value, sizeMethod, context) ?: 0
for (it in 0 until size) {
val reference = objectValue(value, getMethod, context, context.vm.mirrorOf(it)) ?: continue
list.add(reference)
}
return MirrorOfJavaLangList(value, list)
}
}
data class MirrorOfJavaLangList(val that: ObjectReference, val values: List<ObjectReference>)
@@ -75,7 +75,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
val index = selectedIndex
if (index >= 0) {
val selection = model.getElementAt(index) as CoroutineInfoData
AnalyzeStacktraceUtil.printStacktrace(consoleView, selection.stringStackTrace)
AnalyzeStacktraceUtil.printStacktrace(consoleView, stringStackTrace(selection))
} else {
AnalyzeStacktraceUtil.printStacktrace(consoleView, "")
}
@@ -144,7 +144,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
var index = 0
val states = if (UISettings.instance.state.mergeEqualStackTraces) mergedDump else dump
for (state in states) {
if (StringUtil.containsIgnoreCase(state.stringStackTrace, text) || StringUtil.containsIgnoreCase(state.key.name, text)) {
if (StringUtil.containsIgnoreCase(stringStackTrace(state), text) || StringUtil.containsIgnoreCase(state.key.name, text)) {
model.addElement(state)
if (selection === state) {
selectedIndex = index
@@ -263,7 +263,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
val buf = StringBuilder()
buf.append(KotlinDebuggerCoroutinesBundle.message("coroutine.dump.full.title")).append("\n\n")
for (state in myCoroutinesDump) {
buf.append(state.stringStackTrace).append("\n\n")
buf.append(stringStackTrace(state)).append("\n\n")
}
CopyPasteManager.getInstance().setContents(StringSelection(buf.toString()))
@@ -283,7 +283,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
override fun getReportText() = buildString {
for (state in infoData)
append(state.stringStackTrace).append("\n\n")
append(stringStackTrace(state)).append("\n\n")
}
override fun getDefaultFilePath() = (myProject.basePath ?: "") + File.separator + defaultReportFileName
@@ -292,4 +292,13 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
private val defaultReportFileName = "coroutines_report.txt"
}
}
}
private fun stringStackTrace(info: CoroutineInfoData) =
buildString {
appendln("\"${info.key.name}\", state: ${info.key.state}")
info.stackTrace.forEach {
appendln("\t$it")
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.view
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaExecutionStack
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.jdi.ClassesByNameProvider
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.ide.CommonActionsManager
import com.intellij.openapi.Disposable
@@ -57,8 +58,6 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
val someCombobox = ComboBox<String>()
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
// val javaDebugProcess =
// val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
val renderer = SimpleColoredTextIconPresentationRenderer()
val managerThreadExecutor = ManagerThreadExecutor(session)
var treeState: XDebuggerTreeState? = null
@@ -149,14 +148,32 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
inner class XCoroutinesRootNode(suspendContext: SuspendContextImpl) :
XValueContainerNode<CoroutineGroupContainer>(
panel.tree, null, false,
CoroutineGroupContainer(suspendContext, KotlinDebuggerCoroutinesBundle.message("coroutine.view.default.group"))
CoroutineGroupContainer(suspendContext)
)
inner class CoroutineGroupContainer(val suspendContext: SuspendContextImpl, val groupName: String) : XValueContainer() {
inner class CoroutineGroupContainer(val suspendContext: SuspendContextImpl) : XValueContainer() {
override fun computeChildren(node: XCompositeNode) {
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
node.addChildren(groups, true)
managerThreadExecutor.on(suspendContext).schedule {
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
val defaultGroupName = KotlinDebuggerCoroutinesBundle.message("coroutine.view.default.group")
var coroutineCache = debugProbesProxy.dumpCoroutines()
if (coroutineCache.isOk()) {
val children = XValueChildrenList()
var groups = coroutineCache.cache.groupBy { it.key.dispatcher }
for (dispatcher in groups.keys) {
children.add(CoroutineContainer(suspendContext, dispatcher ?: defaultGroupName, groups[dispatcher]))
}
if (children.size() > 0)
node.addChildren(children, true)
else
node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true)
} else {
val errorNode = ErrorNode("coroutine.view.fetching.error")
node.addChildren(XValueChildrenList.singleton(errorNode), true)
}
}
} else {
node.addChildren(
XValueChildrenList.singleton(ErrorNode("to.enable.information.breakpoint.suspend.policy.should.be.set.to.all.threads")),
@@ -168,28 +185,21 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
inner class CoroutineContainer(
val suspendContext: SuspendContextImpl,
val groupName: String
val groupName: String,
val coroutines: List<CoroutineInfoData>?
) : RendererContainer(renderer.renderGroup(groupName)) {
override fun computeChildren(node: XCompositeNode) {
managerThreadExecutor.on(suspendContext).schedule {
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
var coroutineCache = debugProbesProxy.dumpCoroutines()
if (coroutineCache.isOk()) {
val children = XValueChildrenList()
for (coroutineInfo in coroutineCache.cache) {
children.add(FramesContainer(coroutineInfo, suspendContext))
}
if (children.size() > 0)
node.addChildren(children, true)
else
node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true)
} else {
val errorNode = ErrorNode("coroutine.view.fetching.error")
node.addChildren(XValueChildrenList.singleton(errorNode), true)
val children = XValueChildrenList()
if (coroutines != null)
for (coroutineInfo in coroutines) {
children.add(FramesContainer(coroutineInfo, suspendContext))
}
}
if (children.size() > 0)
node.addChildren(children, true)
else
node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true)
}
}
@@ -211,7 +221,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
for (frame in stackFrames) {
if (frame is CreationCoroutineStackFrameItem)
creationStack.add(frame)
else if (frame is CoroutineStackFrameItem)
else
children.add(CoroutineFrameValue(infoData, frame))
}
if (creationStack.isNotEmpty())
@@ -66,22 +66,23 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
debugProcess,
isCurrentContext
)
val jStackFrame = executionStack.createStackFrame(stackFrameItem.frame)
createStackAndSetFrame(threadProxy, { jStackFrame }, isCurrentContext)
createStackAndSetFrame(threadProxy, { executionStack.createStackFrame(stackFrameItem.frame) }, isCurrentContext)
}
is CreationCoroutineStackFrameItem -> {
val position = stackFrameItem.stackTraceElement.findPosition(session.project) ?: return false
val threadProxy = suspendContext.thread ?: return false
val realFrame = threadProxy.forceFrames().first() ?: return false
createStackAndSetFrame(threadProxy, {
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), emptyList(), position)
})
}
is SuspendCoroutineStackFrameItem -> {
val threadProxy = suspendContext.thread ?: return false
val realFrame = threadProxy.forceFrames().first() ?: return false
val lastFrame = valueContainer.infoData.lastObservedFrameFieldRef ?: return false
createStackAndSetFrame(threadProxy, { createSyntheticStackFrame(suspendContext, stackFrameItem, realFrame, lastFrame) })
createStackAndSetFrame(threadProxy, {
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
createSyntheticStackFrame(suspendContext, stackFrameItem, realFrame, lastFrame)
})
}
is RestoredCoroutineStackFrameItem -> {
val threadProxy = stackFrameItem.frame.threadProxy()
@@ -95,8 +96,8 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
val threadProxy = suspendContext.thread ?: return false
val position = stackFrameItem.location.findPosition(session.project)
?: return false
val realFrame = threadProxy.forceFrames().first() ?: return false
createStackAndSetFrame(threadProxy, {
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), stackFrameItem.spilledVariables, position)
})
}
@@ -6,12 +6,10 @@
package org.jetbrains.kotlin.idea.debugger.test
import com.intellij.debugger.engine.AsyncStackTraceProvider
import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.extensions.Extensions
import io.ktor.util.findAllSupertypes
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
@@ -38,9 +36,10 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
val frameProxy = this.frameProxy
if (frameProxy != null) {
try {
val stackTrace = asyncStackTraceProvider.lookupForResumeContinuation(frameProxy, this)
if (stackTrace != null && stackTrace.isNotEmpty()) {
print(renderAsyncStackTrace(stackTrace), ProcessOutputTypes.SYSTEM)
val coroutineInfoData =
asyncStackTraceProvider.lookupForResumeContinuation(frameProxy, this, emptyList())?.coroutineInfoData
if (coroutineInfoData != null && coroutineInfoData.stackTrace.isNotEmpty()) {
print(renderAsyncStackTrace(coroutineInfoData.stackTrace), ProcessOutputTypes.SYSTEM)
} else {
println("No async stack trace available", ProcessOutputTypes.SYSTEM)
}
@@ -128,6 +128,9 @@ sealed class BaseExecutionContext(val evaluationContext: EvaluationContextImpl)
fun findClassSafe(className: String): ClassType? =
hopelessAware { findClass(className) as? ClassType }
fun invokeMethodSafe(type: ClassType, method: Method, args: List<Value?>): Value? {
return hopelessAware { debugProcess.invokeMethod(evaluationContext, type, method, args) }
}
fun invokeMethodAsString(instance: ObjectReference, methodName: String): String? =
(findAndInvoke(instance, instance.referenceType(), methodName, "()Ljava/lang/String;") as? StringReference)?.value() ?: null