[coroutine][debugger] implementation basing on X-* approach

XCoroutine view added for direct comparison with Coroutines
This commit is contained in:
Vladimir Ilmov
2019-12-18 12:50:49 +01:00
committed by Vladimir Ilmov
parent 61c5ef61cc
commit b1b0817336
20 changed files with 672 additions and 432 deletions
@@ -8,10 +8,11 @@ package org.jetbrains.kotlin.idea.debugger.coroutines
import com.intellij.debugger.engine.* import com.intellij.debugger.engine.*
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem import com.intellij.xdebugger.frame.XSuspendContext
import com.sun.jdi.* import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.* import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineAsyncStackFrameItem import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineAsyncStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.AsyncStackTraceContext
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider { class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
@@ -21,7 +22,12 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
return null return null
} }
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: SuspendContext): List<CoroutineAsyncStackFrameItem> { fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem>? {
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList()
return null
}
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem> {
val defaultResult = emptyList<CoroutineAsyncStackFrameItem>() val defaultResult = emptyList<CoroutineAsyncStackFrameItem>()
val location = frameProxy.location() val location = frameProxy.location()
@@ -31,7 +37,7 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
val method = location.safeMethod() ?: return defaultResult val method = location.safeMethod() ?: return defaultResult
val threadReference = frameProxy.threadProxy().threadReference val threadReference = frameProxy.threadProxy().threadReference
if (threadReference == null || !threadReference.isSuspended || !(suspendContext.debugProcess as DebugProcessImpl).canRunEvaluation) if (threadReference == null || !threadReference.isSuspended || !canRunEvaluation(suspendContext))
return defaultResult return defaultResult
@@ -41,7 +47,7 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
private fun createAsyncStackTraceContext( private fun createAsyncStackTraceContext(
frameProxy: StackFrameProxyImpl, frameProxy: StackFrameProxyImpl,
suspendContext: SuspendContext, suspendContext: XSuspendContext,
method: Method method: Method
): AsyncStackTraceContext { ): AsyncStackTraceContext {
val evaluationContext = EvaluationContextImpl(suspendContext as SuspendContextImpl, frameProxy) val evaluationContext = EvaluationContextImpl(suspendContext as SuspendContextImpl, frameProxy)
@@ -49,5 +55,8 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
// DebugMetadataKt not found, probably old kotlin-stdlib version // DebugMetadataKt not found, probably old kotlin-stdlib version
return AsyncStackTraceContext(context, method) return AsyncStackTraceContext(context, method)
} }
fun canRunEvaluation(suspendContext: XSuspendContext) =
(suspendContext as SuspendContextImpl).debugProcess.canRunEvaluation
} }
@@ -13,6 +13,7 @@ import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
@Deprecated("moved to XCoroutineView")
open class BuildCoroutineNodeCommand( open class BuildCoroutineNodeCommand(
val node: DebuggerTreeNodeImpl, val node: DebuggerTreeNodeImpl,
debuggerContext: DebuggerContextImpl, debuggerContext: DebuggerContextImpl,
@@ -11,6 +11,7 @@ import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CreationFramesDescriptor import org.jetbrains.kotlin.idea.debugger.coroutines.data.CreationFramesDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineCreatedStackFrameDescriptor import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineCreatedStackFrameDescriptor
@Deprecated("moved to XCoroutineView")
class CoroutineBuildCreationFrameCommand( class CoroutineBuildCreationFrameCommand(
node: DebuggerTreeNodeImpl, node: DebuggerTreeNodeImpl,
val descriptor: CreationFramesDescriptor, val descriptor: CreationFramesDescriptor,
@@ -21,10 +22,11 @@ class CoroutineBuildCreationFrameCommand(
val threadProxy = debuggerContext.suspendContext?.thread ?: return val threadProxy = debuggerContext.suspendContext?.thread ?: return
val evalContext = debuggerContext.createEvaluationContext() ?: return val evalContext = debuggerContext.createEvaluationContext() ?: return
val proxy = threadProxy.forceFrames().first() val proxy = threadProxy.forceFrames().first()
descriptor.frames.forEach { for(it in descriptor.frames) {
val descriptor = myNodeManager.createNode( val descriptor = myNodeManager.createNode(
CoroutineCreatedStackFrameDescriptor(it, proxy), evalContext) CoroutineCreatedStackFrameDescriptor(it, proxy), evalContext)
myChildren.add(descriptor) myChildren.add(descriptor)
} }
updateUI(true) updateUI(true)
} }
@@ -89,13 +89,13 @@ class CoroutineBuildFrameCommand(
debugProcess: DebugProcessImpl, debugProcess: DebugProcessImpl,
evalContext: EvaluationContextImpl evalContext: EvaluationContextImpl
): Boolean { ): Boolean {
if (descriptor.infoData.thread == null) { if (descriptor.infoData.activeThread == null) {
myChildren.add(myNodeManager.createMessageNode("Frames are not available")) myChildren.add(myNodeManager.createMessageNode("Frames are not available"))
return true return true
} }
val proxy = ThreadReferenceProxyImpl( val proxy = ThreadReferenceProxyImpl(
debugProcess.virtualMachineProxy, debugProcess.virtualMachineProxy,
descriptor.infoData.thread descriptor.infoData.activeThread
) )
val frames = proxy.forceFrames() val frames = proxy.forceFrames()
var endRange = findResumeWithMethodFrameIndex(frames) var endRange = findResumeWithMethodFrameIndex(frames)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.debugger.coroutines.command package org.jetbrains.kotlin.idea.debugger.coroutines.command
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.* import com.intellij.debugger.engine.*
import com.intellij.debugger.jdi.ClassesByNameProvider import com.intellij.debugger.jdi.ClassesByNameProvider
import com.intellij.debugger.jdi.GeneratedLocation import com.intellij.debugger.jdi.GeneratedLocation
@@ -13,40 +14,46 @@ import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.debugger.ui.impl.watch.MethodsTracker import com.intellij.debugger.ui.impl.watch.MethodsTracker
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.openapi.project.Project
import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.ContainerUtil
import com.intellij.xdebugger.XSourcePosition import com.intellij.xdebugger.XSourcePosition
import com.intellij.xdebugger.frame.XExecutionStack
import com.intellij.xdebugger.frame.XStackFrame import com.intellij.xdebugger.frame.XStackFrame
import com.sun.jdi.AbsentInformationException import com.intellij.xdebugger.frame.XSuspendContext
import com.sun.jdi.Location import com.sun.jdi.*
import com.sun.jdi.ReferenceType import org.jetbrains.kotlin.idea.debugger.*
import com.sun.jdi.ThreadReference
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineAsyncStackTraceProvider import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineAsyncStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutines.util.getPosition import org.jetbrains.kotlin.idea.debugger.coroutines.data.SuspendStackFrameDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SyntheticStackFrame
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.AsyncStackTraceContext
import org.jetbrains.kotlin.idea.debugger.coroutines.util.EmptyStackFrameDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
class CoroutineBuilder(val suspendContext: SuspendContextImpl) { class CoroutineBuilder(val suspendContext: XSuspendContext) {
private val methodsTracker = MethodsTracker() private val methodsTracker = MethodsTracker()
private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider() private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
val virtualMachineProxy = suspendContext.debugProcess.virtualMachineProxy val debugProcess = (suspendContext as SuspendContextImpl).debugProcess
val virtualMachineProxy = debugProcess.virtualMachineProxy
val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses()) val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses())
companion object { companion object {
val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used for creation stacktrace separator in kotlinx.coroutines val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used for creation stacktrace separator in kotlinx.coroutines
} }
fun build(ci: CoroutineInfoData): List<CoroutineStackFrameItem> { fun build(coroutine: CoroutineInfoData): List<CoroutineStackFrameItem> {
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>() val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
val suspendedThreadProxy = suspendedThreadProxy()
val firstSuspendedStackFrameProxyImpl = firstSuspendedThreadFrame() val firstSuspendedStackFrameProxyImpl = firstSuspendedThreadFrame()
val creationFrameSeparatorIndex = findCreationFrameIndex(ci.stackTrace) val creationFrameSeparatorIndex = findCreationFrameIndex(coroutine.stackTrace)
val runningThreadReferenceProxyImpl = currentRunningThreadProxy(ci.thread ?: suspendedThreadProxy().threadReference) val positionManager = debugProcess.positionManager
val positionManager = suspendContext.debugProcess.positionManager
if (ci.state == CoroutineInfoData.State.RUNNING && runningThreadReferenceProxyImpl is ThreadReferenceProxyImpl) { if (coroutine.state == CoroutineInfoData.State.RUNNING && coroutine.activeThread is ThreadReference) {
val executionStack = JavaExecutionStack(runningThreadReferenceProxyImpl, suspendContext.debugProcess, suspendContext.thread == runningThreadReferenceProxyImpl) val threadReferenceProxyImpl = runningThreadProxy(coroutine.activeThread)
val executionStack = JavaExecutionStack(threadReferenceProxyImpl, debugProcess, suspendedSameThread(coroutine.activeThread))
val frames = runningThreadReferenceProxyImpl.forceFrames() val frames = threadReferenceProxyImpl.forceFrames()
var resumeMethodIndex = findResumeMethodIndex(frames) var resumeMethodIndex = findResumeMethodIndex(frames)
for (frameIndex in 0..frames.lastIndex) { for (frameIndex in 0..frames.lastIndex) {
val runningStackFrameProxy = frames[frameIndex] val runningStackFrameProxy = frames[frameIndex]
@@ -55,47 +62,46 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
val previousJavaFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true) val previousJavaFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true)
val asyncStackTrace = coroutineStackFrameProvider val asyncStackTrace = coroutineStackFrameProvider
.getAsyncStackTrace(previousJavaFrame, suspendContext) .getAsyncStackTrace(previousJavaFrame, suspendContext)
asyncStackTrace?.forEach { asyncStackTrace?.forEach { asyncFrame ->
// val xStackFrame = positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, it.location)
val xStackFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true) val xStackFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true)
// val itStackFrame = null coroutineStackFrameList.add(AsyncCoroutineStackFrameItem(runningStackFrameProxy, asyncFrame, xStackFrame))
coroutineStackFrameList.add(AsyncCoroutineStackFrameItem(runningStackFrameProxy, "some label", it, xStackFrame))
} }
} else { } else {
// val xStackFrame = stackFrame(positionManager, runningStackFrameProxy, runningStackFrameProxy.location())
val xStackFrame = executionStack.createStackFrame(runningStackFrameProxy) val xStackFrame = executionStack.createStackFrame(runningStackFrameProxy)
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, "", xStackFrame)) coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, xStackFrame))
} }
} }
} else if (ci.state == CoroutineInfoData.State } else if (coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) {
.SUSPENDED || runningThreadReferenceProxyImpl == null // to get frames from CoroutineInfo anyway
) { // to get frames from CoroutineInfo anyway
// the thread is paused on breakpoint - it has at least one frame // the thread is paused on breakpoint - it has at least one frame
ci.stackTrace.subList(0, creationFrameSeparatorIndex).forEach { val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex + 1)
val xStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, it) for (suspendedFrame in suspendedStackTrace) {
// val xStackFrame = null val suspendedXStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, suspendedFrame)
coroutineStackFrameList.add(SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, "suspended frame", it, xStackFrame)) coroutineStackFrameList.add(
SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame)
)
} }
} }
val executionStack = JavaExecutionStack(suspendedThreadProxy(), suspendContext.debugProcess, false) val executionStack = JavaExecutionStack(suspendedThreadProxy(), debugProcess, false)
val xStackFrame = executionStack.createStackFrame(firstSuspendedStackFrameProxyImpl) val xStackFrame = executionStack.createStackFrame(firstSuspendedStackFrameProxyImpl)
ci.stackTrace.subList(creationFrameSeparatorIndex + 1, ci.stackTrace.size).forEach { coroutine.stackTrace.subList(creationFrameSeparatorIndex + 1, coroutine.stackTrace.size).forEach {
var location = createLocation(it) var location = createLocation(it)
coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, "creation frame", it, xStackFrame, location)) coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, it, xStackFrame, location))
} }
ci.stackFrameList.addAll(coroutineStackFrameList) coroutine.stackFrameList.addAll(coroutineStackFrameList)
return coroutineStackFrameList return coroutineStackFrameList
} }
fun createLocation(stackTraceElement: StackTraceElement): Location { private fun suspendedSameThread(activeThread: ThreadReference) =
return findLocation( activeThread == suspendedThreadProxy().threadReference
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
stackTraceElement.methodName, private fun createLocation(stackTraceElement: StackTraceElement): Location = findLocation(
stackTraceElement.lineNumber ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
) stackTraceElement.methodName,
} stackTraceElement.lineNumber
)
private fun findLocation( private fun findLocation(
type: ReferenceType?, type: ReferenceType?,
@@ -113,17 +119,16 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
} catch (ignored: AbsentInformationException) { } catch (ignored: AbsentInformationException) {
} }
} }
return GeneratedLocation(suspendContext.debugProcess, type, methodName, line) return GeneratedLocation(debugProcess, type, methodName, line)
} }
fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, location: Location): XStackFrame { fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, location: Location): XStackFrame {
return positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, location)!! return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!!
} }
fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, stackTraceElement: StackTraceElement) : XStackFrame { fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, stackTraceElement: StackTraceElement) : XStackFrame {
val location = createLocation(stackTraceElement) val location = createLocation(stackTraceElement)
// val sourcePosition = getPosition(stackTraceElement) return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!!
return positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, location)!!
} }
/** /**
@@ -143,28 +148,29 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
private fun firstSuspendedThreadFrame(): StackFrameProxyImpl = private fun firstSuspendedThreadFrame(): StackFrameProxyImpl =
suspendedThreadProxy().forceFrames().first() suspendedThreadProxy().forceFrames().first()
// retrieves currently suspended but active and executing corouting thread proxy // retrieves currently suspended but active and executing coroutine thread proxy
fun currentRunningThreadProxy(threadReference: ThreadReference?): ThreadReferenceProxyImpl? = fun runningThreadProxy(threadReference: ThreadReference) =
ThreadReferenceProxyImpl(suspendContext.debugProcess.virtualMachineProxy, threadReference) ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, threadReference)
// retrieves current suspended thread proxy // retrieves current suspended thread proxy
fun suspendedThreadProxy(): ThreadReferenceProxyImpl = private fun suspendedThreadProxy(): ThreadReferenceProxyImpl =
suspendContext.thread!! // @TODO hash replace !! (suspendContext as SuspendContextImpl).thread!! // @TODO hash replace !!
private fun findResumeMethodIndex(frames: List<StackFrameProxyImpl>): Int { private fun findResumeMethodIndex(frames: List<StackFrameProxyImpl>): Int {
for (j: Int in frames.lastIndex downTo 0) for (i: Int in frames.lastIndex downTo 0)
if (isResumeMethodFrame(frames[j])) { if (isResumeMethodFrame(frames[i])) {
return j return i
} }
return 0 return 0
} }
private fun isResumeMethodFrame(frame: StackFrameProxyImpl) = frame.location().method().name() == "resumeWith" private fun isResumeMethodFrame(frame: StackFrameProxyImpl) =
frame.safeLocation()?.safeMethod()?.name() == "resumeWith"
/** /**
* Should be invoked on manager thread * Should be invoked on manager thread
* This code was migrated from previous implementation, has to be refactored @TODO
*/ */
/*
private fun createSyntheticStackFrame( private fun createSyntheticStackFrame(
descriptor: SuspendStackFrameDescriptor, descriptor: SuspendStackFrameDescriptor,
pos: XSourcePosition, pos: XSourcePosition,
@@ -191,50 +197,59 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
} ?: return null } ?: return null
return executionStack to SyntheticStackFrame(descriptor, vars, pos) return executionStack to SyntheticStackFrame(descriptor, vars, pos)
} }
*/
} }
class CreationCoroutineStackFrameItem( class CreationCoroutineStackFrameItem(
frame: StackFrameProxyImpl, frame: StackFrameProxyImpl,
label: String = "",
val stackTraceElement: StackTraceElement, val stackTraceElement: StackTraceElement,
stackFrame: XStackFrame, stackFrame: XStackFrame,
val location: Location val location: Location
) : CoroutineStackFrameItem(frame, label, stackFrame) { ) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() = location override fun location() = location
fun emptyDescriptor() =
EmptyStackFrameDescriptor(stackTraceElement, frame)
} }
class SuspendCoroutineStackFrameItem( class SuspendCoroutineStackFrameItem(
frame: StackFrameProxyImpl, frame: StackFrameProxyImpl,
label: String = "",
val stackTraceElement: StackTraceElement, val stackTraceElement: StackTraceElement,
stackFrame: XStackFrame stackFrame: XStackFrame
) : CoroutineStackFrameItem(frame, label, stackFrame) { ) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() = frame.location() override fun location() = frame.location()
} }
class AsyncCoroutineStackFrameItem( class AsyncCoroutineStackFrameItem(
frame: StackFrameProxyImpl, frame: StackFrameProxyImpl,
label: String = "",
val frameItem: StackFrameItem, val frameItem: StackFrameItem,
stackFrame: XStackFrame stackFrame: XStackFrame
) : CoroutineStackFrameItem(frame, label, stackFrame) { ) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() : Location = frame.location() override fun location() : Location = frame.location()
} }
class RunningCoroutineStackFrameItem( class RunningCoroutineStackFrameItem(
frame: StackFrameProxyImpl, frame: StackFrameProxyImpl,
label: String = "",
stackFrame: XStackFrame stackFrame: XStackFrame
) : CoroutineStackFrameItem(frame, label, stackFrame) { ) : CoroutineStackFrameItem(frame, stackFrame) {
val location = frame.location() // it should be invoked in manager thread val location = frame.location() // it should be invoked in manager thread
override fun location() = location override fun location() = location
} }
abstract class CoroutineStackFrameItem(val frame: StackFrameProxyImpl, val label: String = "", val stackFrame: XStackFrame) { abstract class CoroutineStackFrameItem(val frame: StackFrameProxyImpl, val stackFrame: XStackFrame) {
val log by logger
fun sourcePosition() : XSourcePosition? = stackFrame.sourcePosition fun sourcePosition() : XSourcePosition? = stackFrame.sourcePosition
abstract fun location(): Location abstract fun location(): Location
fun uniqueId(): String {
val location = location()
try {
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
location.safeLineNumber() + ":" + location.safeSourceLineNumber()
} catch (e: Exception) {
log.error(e)
return location.method().toString() + ":" + location.lineNumber()
}
}
} }
@@ -17,7 +17,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.MessageType import com.intellij.openapi.ui.MessageType
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
@@ -29,10 +28,10 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutineDumpPanel
import org.jetbrains.kotlin.idea.debugger.coroutines.coroutineDebuggerEnabled import org.jetbrains.kotlin.idea.debugger.coroutines.coroutineDebuggerEnabled
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
@Suppress("ComponentNotRegistered") @Suppress("ComponentNotRegistered")
class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate { class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
private val logger = Logger.getInstance(this::class.java)
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return val project = e.project ?: return
@@ -42,20 +41,15 @@ class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
val process = context.debugProcess ?: return val process = context.debugProcess ?: return
process.managerThread.schedule(object : SuspendContextCommandImpl(context.suspendContext) { process.managerThread.schedule(object : SuspendContextCommandImpl(context.suspendContext) {
override fun contextAction() { override fun contextAction() {
val states = CoroutinesDebugProbesProxy(context.suspendContext!!).dumpCoroutines() ?: return val states = CoroutinesDebugProbesProxy(context.suspendContext ?: return)
.dumpCoroutines()
if (states.isOk()) { if (states.isOk()) {
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification( val message = KotlinBundle.message("debugger.session.tab.coroutine.message.error")
KotlinBundle.message("debugger.session.tab.coroutine.message.error"), XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message,MessageType.ERROR).notify(project)
MessageType.ERROR
).notify(project)
} else { } else {
val f = fun() { val f = fun() {
addCoroutineDump( val ui = session.xDebugSession?.ui ?: return
project, addCoroutineDump(project, states.cache, ui, session.searchScope)
states.cache,
session.xDebugSession?.ui ?: return,
session.searchScope
)
} }
ApplicationManager.getApplication().invokeLater(f, ModalityState.NON_MODAL) ApplicationManager.getApplication().invokeLater(f, ModalityState.NON_MODAL)
} }
@@ -13,60 +13,57 @@ import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.impl.DebuggerSession import com.intellij.debugger.impl.DebuggerSession
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
import com.intellij.openapi.ui.MessageType
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineDescriptorData import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineDescriptorData
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ManagerThreadExecutor import org.jetbrains.kotlin.idea.debugger.coroutines.util.ProjectNotification
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.createEvaluationContext
import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutinesDebuggerTree import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutinesDebuggerTree
@Deprecated("moved to XCoroutineView")
class RefreshCoroutinesTreeCommand( class RefreshCoroutinesTreeCommand(
val context: DebuggerContextImpl, val context: DebuggerContextImpl,
private val debuggerTree: CoroutinesDebuggerTree private val debuggerTree: CoroutinesDebuggerTree
) : SuspendContextCommandImpl(context.suspendContext) { ) : SuspendContextCommandImpl(context.suspendContext) {
val notification = ProjectNotification(debuggerTree.project)
override fun contextAction() { override fun contextAction() {
val nf = debuggerTree.nodeFactory val nodeManagerImpl = debuggerTree.nodeFactory
val root = nf.defaultNode val root = nodeManagerImpl.defaultNode
val sc: SuspendContextImpl? = suspendContext val suspendContext: SuspendContextImpl? = suspendContext
if (context.debuggerSession is DebuggerSession && sc is SuspendContextImpl && !sc.isResumed) { if (context.debuggerSession is DebuggerSession && suspendContext is SuspendContextImpl && !suspendContext.isResumed) {
val infoCache = CoroutinesDebugProbesProxy(sc).dumpCoroutines() var infoCache = CoroutinesDebugProbesProxy(suspendContext).dumpCoroutines()
if (infoCache.isOk()) { if (infoCache.isOk()) {
val evaluationContext = sc.createEvaluationContext() val evaluationContext = evaluationContext(suspendContext)
for (state in infoCache.cache) { for (state in infoCache.cache) {
val descriptor = createCoroutineDescriptorNode(nf, state, evaluationContext) val descriptor = createCoroutineDescriptorNode(nodeManagerImpl, state, evaluationContext)
root.add(descriptor) root.add(descriptor)
} }
setRoot(root) setRoot(root)
} else { } else {
debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.failure")) debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.failure"))
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification( notification.error(KotlinBundle.message("debugger.session.tab.coroutine.message.error"))
KotlinBundle.message("debugger.session.tab.coroutine.message.error"),
MessageType.ERROR
)
.notify(debuggerTree.project)
} }
} else { } else
debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.resume")) debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.resume"))
}
} }
private fun evaluationContext(suspendContext : SuspendContextImpl) =
EvaluationContextImpl(suspendContext, suspendContext.frameProxy)
private fun createCoroutineDescriptorNode( private fun createCoroutineDescriptorNode(
nodeFactory: NodeManagerImpl, nodeFactory: NodeManagerImpl,
coroutineInfoData: CoroutineInfoData, coroutineInfoData: CoroutineInfoData,
evaluationContext: EvaluationContextImpl evaluationContext: EvaluationContextImpl
) = ): DebuggerTreeNodeImpl {
nodeFactory.createNode( return nodeFactory.createNode(
nodeFactory.getDescriptor( nodeFactory.getDescriptor(
null, null,
CoroutineDescriptorData(coroutineInfoData) CoroutineDescriptorData(coroutineInfoData)
), ),
evaluationContext evaluationContext
) )
}
private fun setRoot(root: DebuggerTreeNodeImpl) { private fun setRoot(root: DebuggerTreeNodeImpl) {
DebuggerInvocationUtil.swingInvokeLater(debuggerTree.project) { DebuggerInvocationUtil.swingInvokeLater(debuggerTree.project) {
@@ -12,6 +12,4 @@ import com.intellij.xdebugger.frame.XNamedValue
class CoroutineAsyncStackFrameItem( class CoroutineAsyncStackFrameItem(
val location: GeneratedLocation, val location: GeneratedLocation,
spilledVariables: List<XNamedValue> spilledVariables: List<XNamedValue>
) : StackFrameItem(location, spilledVariables) { ) : StackFrameItem(location, spilledVariables)
}
@@ -15,12 +15,10 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineStackFrame
data class CoroutineInfoData( data class CoroutineInfoData(
val name: String, val name: String,
val state: State, val state: State,
val threadName: String,
val threadStatus: Int,
val stackTrace: List<StackTraceElement>, val stackTrace: List<StackTraceElement>,
// links to jdi.* references // links to jdi.* references
val thread: ThreadReference? = null, // for suspended coroutines should be null val activeThread: ThreadReference? = null, // for suspended coroutines should be null
val frame: ObjectReference? val frame: ObjectReference?
) { ) {
var stackFrameList = mutableListOf<CoroutineStackFrameItem>() var stackFrameList = mutableListOf<CoroutineStackFrameItem>()
@@ -19,6 +19,7 @@ import com.intellij.icons.AllIcons
import com.sun.jdi.ObjectReference import com.sun.jdi.ObjectReference
import javax.swing.Icon import javax.swing.Icon
@Deprecated("moved to XCoroutineView")
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() { class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
lateinit var icon: Icon lateinit var icon: Icon
@@ -26,10 +27,11 @@ class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorI
@Throws(EvaluateException::class) @Throws(EvaluateException::class)
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String { override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
val thread = infoData.thread val thread = infoData.activeThread
val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: "" val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: ""
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else "" val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
return "${infoData.name}: ${infoData.state}${if (name.isNotEmpty()) " on thread \"$name\":$threadState" else ""}" val threadName = if (name.isNotEmpty()) " on thread \"$name\":$threadState" else ""
return "${infoData.name}: ${infoData.state} $threadName"
} }
override fun isExpandable() = infoData.state != CoroutineInfoData.State.CREATED override fun isExpandable() = infoData.state != CoroutineInfoData.State.CREATED
@@ -45,8 +47,9 @@ class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorI
} }
} }
class CreationFramesDescriptor(val frames: List<StackTraceElement>) @Deprecated("moved to XCoroutineView")
: MessageDescriptor("Coroutine creation stack trace", INFORMATION) { class CreationFramesDescriptor(val frames: List<StackTraceElement>) :
MessageDescriptor("Coroutine creation stack trace", INFORMATION) {
override fun isExpandable() = true override fun isExpandable() = true
} }
@@ -54,6 +57,7 @@ class CreationFramesDescriptor(val frames: List<StackTraceElement>)
/** /**
* Descriptor for suspend functions * Descriptor for suspend functions
*/ */
@Deprecated("moved to XCoroutineView")
class SuspendStackFrameDescriptor( class SuspendStackFrameDescriptor(
val infoData: CoroutineInfoData, val infoData: CoroutineInfoData,
val frame: StackTraceElement, val frame: StackTraceElement,
@@ -64,8 +68,8 @@ class SuspendStackFrameDescriptor(
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String { override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
return with(frame) { return with(frame) {
val pack = className.substringBeforeLast(".", "") val pack = className.substringBeforeLast(".", "")
"$methodName:$lineNumber, ${className.substringAfterLast(".")} " + val packDisplay = if (pack.isNotEmpty()) "{$pack}" else ""
if (pack.isNotEmpty()) "{$pack}" else "" "$methodName:$lineNumber, ${className.substringAfterLast(".")} $packDisplay"
} }
} }
@@ -76,32 +80,35 @@ class SuspendStackFrameDescriptor(
/** /**
* For the case when no data inside frame is available * For the case when no data inside frame is available
*/ */
@Deprecated("moved to XCoroutineView")
class CoroutineCreatedStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) : class CoroutineCreatedStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
CoroutineStackFrameDescriptor(proxy) { CoroutineStackFrameDescriptor(proxy) {
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String { override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
return with(frame) { return with(frame) {
val pack = className.substringBeforeLast(".", "") val pack = className.substringBeforeLast(".", "")
"$methodName:$lineNumber, ${className.substringAfterLast(".")} ${if (pack.isNotEmpty()) "{$pack}" else ""}" val packDisplay = if (pack.isNotEmpty()) "{$pack}" else ""
"$methodName:$lineNumber, ${className.substringAfterLast(".")} $packDisplay"
} }
} }
override fun getName() = null override fun getName() = null
} }
@Deprecated("moved to XCoroutineView")
class AsyncStackFrameDescriptor(val infoData: CoroutineInfoData, val frame: StackFrameItem, proxy: StackFrameProxyImpl) : class AsyncStackFrameDescriptor(val infoData: CoroutineInfoData, val frame: StackFrameItem, proxy: StackFrameProxyImpl) :
CoroutineStackFrameDescriptor(proxy) { CoroutineStackFrameDescriptor(proxy) {
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String { override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
return with(frame) { return with(frame) {
val pack = path().substringBeforeLast(".", "") val pack = path().substringBeforeLast(".", "")
"${method()}:${line()}, ${path().substringAfterLast(".")} ${if (pack.isNotEmpty()) "{$pack}" else ""}" val packDisplay = if (pack.isNotEmpty()) "{$pack}" else ""
"${method()}:${line()}, ${path().substringAfterLast(".")} $packDisplay"
} }
} }
override fun getName() = frame.method() override fun getName() = frame.method()
} }
@Deprecated("moved to XCoroutineView")
open class CoroutineStackFrameDescriptor(proxy: StackFrameProxyImpl) : StackFrameDescriptorImpl(proxy, MethodsTracker()) { open class CoroutineStackFrameDescriptor(proxy: StackFrameProxyImpl) : StackFrameDescriptorImpl(proxy, MethodsTracker()) {
override fun isExpandable() = false override fun isExpandable() = false
} }
@@ -17,8 +17,13 @@ import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.LookupContinuation import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.LookupContinuation
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
class CoroutineStackTraceData(infoData: CoroutineInfoData, proxy: StackFrameProxyImpl, evalContext: EvaluationContextImpl, val frame: StackTraceElement) @Deprecated("moved to XCoroutineView")
: CoroutineStackDescriptorData(infoData, proxy, evalContext) { class CoroutineStackTraceData(
infoData: CoroutineInfoData,
proxy: StackFrameProxyImpl,
evalContext: EvaluationContextImpl,
val frame: StackTraceElement
) : CoroutineStackDescriptorData(infoData, proxy, evalContext) {
override fun hashCode() = frame.hashCode() override fun hashCode() = frame.hashCode()
@@ -34,13 +39,19 @@ class CoroutineStackTraceData(infoData: CoroutineInfoData, proxy: StackFrameProx
return if (continuation is ObjectReference) return if (continuation is ObjectReference)
SuspendStackFrameDescriptor(infoData, frame, proxy, continuation) SuspendStackFrameDescriptor(infoData, frame, proxy, continuation)
else else
CoroutineCreatedStackFrameDescriptor(frame, proxy) CoroutineCreatedStackFrameDescriptor(frame, proxy)
} }
} }
class CoroutineStackFrameData(infoData: CoroutineInfoData, proxy: StackFrameProxyImpl, evalContext: EvaluationContextImpl, val frame: StackFrameItem) @Deprecated("moved to XCoroutineView")
: CoroutineStackDescriptorData(infoData, proxy, evalContext) { class CoroutineStackFrameData(
infoData: CoroutineInfoData,
proxy: StackFrameProxyImpl,
evalContext: EvaluationContextImpl,
val frame: StackFrameItem
) :
CoroutineStackDescriptorData(infoData, proxy, evalContext) {
override fun createDescriptorImpl(project: Project): NodeDescriptorImpl = override fun createDescriptorImpl(project: Project): NodeDescriptorImpl =
AsyncStackFrameDescriptor(infoData, frame, proxy) AsyncStackFrameDescriptor(infoData, frame, proxy)
@@ -49,8 +60,12 @@ class CoroutineStackFrameData(infoData: CoroutineInfoData, proxy: StackFrameProx
override fun equals(other: Any?) = other is CoroutineStackFrameData && frame == other.frame override fun equals(other: Any?) = other is CoroutineStackFrameData && frame == other.frame
} }
abstract class CoroutineStackDescriptorData(val infoData: CoroutineInfoData, val proxy: StackFrameProxyImpl, val evalContext: EvaluationContextImpl) @Deprecated("moved to XCoroutineView")
: DescriptorData<NodeDescriptorImpl>() { abstract class CoroutineStackDescriptorData(
val infoData: CoroutineInfoData,
val proxy: StackFrameProxyImpl,
val evalContext: EvaluationContextImpl
) : DescriptorData<NodeDescriptorImpl>() {
override fun getDisplayKey(): DisplayKey<NodeDescriptorImpl> = SimpleDisplayKey(infoData) override fun getDisplayKey(): DisplayKey<NodeDescriptorImpl> = SimpleDisplayKey(infoData)
} }
@@ -58,6 +73,7 @@ abstract class CoroutineStackDescriptorData(val infoData: CoroutineInfoData, val
/** /**
* Describes coroutine itself in the tree (name: STATE), has children if stacktrace is not empty (state = CREATED) * Describes coroutine itself in the tree (name: STATE), has children if stacktrace is not empty (state = CREATED)
*/ */
@Deprecated("moved to XCoroutineView")
class CoroutineDescriptorData(private val infoData: CoroutineInfoData) : DescriptorData<CoroutineDescriptorImpl>() { class CoroutineDescriptorData(private val infoData: CoroutineInfoData) : DescriptorData<CoroutineDescriptorImpl>() {
override fun createDescriptorImpl(project: Project) = override fun createDescriptorImpl(project: Project) =
@@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * 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.coroutines package org.jetbrains.kotlin.idea.debugger.coroutines.proxy
import com.intellij.debugger.DebuggerContext import com.intellij.debugger.DebuggerContext
import com.intellij.debugger.engine.JavaValue import com.intellij.debugger.engine.JavaValue
@@ -37,7 +37,7 @@ class AsyncStackTraceContext(
val continuation = locateContinuation() ?: return emptyList() val continuation = locateContinuation() ?: return emptyList()
val frames = mutableListOf<CoroutineAsyncStackFrameItem>() val frames = mutableListOf<CoroutineAsyncStackFrameItem>()
collectFramesRecursively(continuation, frames) collectFramesRecursively(continuation, frames)
return frames; return frames
} }
private fun locateContinuation() : ObjectReference? { private fun locateContinuation() : ObjectReference? {
@@ -105,7 +105,7 @@ class AsyncStackTraceContext(
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? { fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
val rawSpilledVariables = val rawSpilledVariables =
context.invokeMethodAsArray(debugMetadataKtType, "getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;", continuation) as? ArrayReference ?: return null context.invokeMethodAsArray(debugMetadataKtType, "getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;", continuation) ?: return null
context.keepReference(rawSpilledVariables) context.keepReference(rawSpilledVariables)
@@ -4,12 +4,10 @@
*/ */
package org.jetbrains.kotlin.idea.debugger.coroutines.proxy package org.jetbrains.kotlin.idea.debugger.coroutines.proxy
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.xdebugger.XDebugProcess import com.intellij.xdebugger.frame.XSuspendContext
import com.sun.jdi.* import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineBuilder import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineBuilder
import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutineInfoCache import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutineInfoCache
@@ -17,16 +15,10 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
fun SuspendContextImpl.createEvaluationContext() = class CoroutinesDebugProbesProxy(val suspendContext: XSuspendContext) {
EvaluationContextImpl(this, this.frameProxy)
class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
private val log by logger private val log by logger
// @TODO refactor to extract initialization logic private var executionContext: ExecutionContext = executionContext()
private var executionContext: ExecutionContext = ExecutionContext(
EvaluationContextImpl(suspendContext, suspendContext.frameProxy),
suspendContext.frameProxy as StackFrameProxyImpl)
// might want to use inner class but also having to monitor order of fields // might want to use inner class but also having to monitor order of fields
private var refs: ProcessReferences = ProcessReferences(executionContext) private var refs: ProcessReferences = ProcessReferences(executionContext)
@@ -89,26 +81,16 @@ class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
val state = getState(instance) val state = getState(instance)
val thread = getLastObservedThread(instance, refs.lastObservedThreadFieldRef) val thread = getLastObservedThread(instance, refs.lastObservedThreadFieldRef)
val lastObservedFrameFieldRef = instance.getValue(refs.lastObservedFrameFieldRef) as? ObjectReference val lastObservedFrameFieldRef = instance.getValue(refs.lastObservedFrameFieldRef) as? ObjectReference
val stackTrace = getStackTrace(instance)
return CoroutineInfoData( return CoroutineInfoData(
name, name,
CoroutineInfoData.State.valueOf(state), CoroutineInfoData.State.valueOf(state),
getThreadName(instance), stackTrace,
getThreadState(instance),
getStackTrace(instance),
thread, thread,
lastObservedFrameFieldRef lastObservedFrameFieldRef
) )
} }
private fun getThreadName(instance: ObjectReference) : String {
return "thread name"
}
private fun getThreadState(instance: ObjectReference) : Int {
return 1
}
private fun getName( private fun getName(
info: ObjectReference // CoroutineInfo instance info: ObjectReference // CoroutineInfo instance
): String { ): String {
@@ -203,6 +185,11 @@ class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
private fun sizeOf(args: ObjectReference): Int = private fun sizeOf(args: ObjectReference): Int =
(executionContext.invokeMethod(args, refs.sizeRef, emptyList()) as IntegerValue).value() (executionContext.invokeMethod(args, refs.sizeRef, emptyList()) as IntegerValue).value()
private fun executionContext() : ExecutionContext {
val evaluationContextImpl = EvaluationContextImpl(suspendContext as SuspendContextImpl, suspendContext.frameProxy)
return ExecutionContext(evaluationContextImpl, suspendContext.frameProxy as StackFrameProxyImpl)
}
/** /**
* @TODO refactor later * @TODO refactor later
* Holds ClassTypes, Methods, ObjectReferences and Fields for a particular jvm * Holds ClassTypes, Methods, ObjectReferences and Fields for a particular jvm
@@ -8,25 +8,49 @@ package org.jetbrains.kotlin.idea.debugger.coroutines.proxy
import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.events.DebuggerCommandImpl import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
import com.intellij.debugger.impl.PrioritizedTask import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.util.Computable import com.intellij.openapi.util.Computable
import com.intellij.xdebugger.frame.XSuspendContext
import java.awt.Component
class ManagerThreadExecutor(val debugProcess: DebugProcessImpl, val priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) { class ManagerThreadExecutor(val debugProcess: DebugProcessImpl) {
fun schedule(f: () -> Unit) { fun on(suspendContext: SuspendContextImpl, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
val runnable = object : Runnable { ManagerThreadExecutorInstance(suspendContext, priority)
override fun run() {f()}
fun on(suspendContext: XSuspendContext, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
ManagerThreadExecutorInstance(suspendContext as SuspendContextImpl, priority)
inner class ManagerThreadExecutorInstance(
val suspendContext: SuspendContextImpl,
val priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL
) {
fun schedule(f: (SuspendContextImpl) -> Unit) {
val suspendContextCommand = object : SuspendContextCommandImpl(suspendContext) {
override fun getPriority() = this@ManagerThreadExecutorInstance.priority
override fun contextAction(suspendContext: SuspendContextImpl) {
f(suspendContext)
}
}
debugProcess.managerThread.invoke(suspendContextCommand)
} }
debugProcess.managerThread.schedule(priority, runnable)
}
fun schedule(f: DebuggerCommandImpl) {
debugProcess.managerThread.schedule(f)
} }
} }
class ApplicationThreadExecutor() { class ApplicationThreadExecutor {
fun <T> invoke(f: () -> T) : T { fun <T> readAction(f: () -> T): T {
return ApplicationManager.getApplication().runReadAction(Computable(f)) return ApplicationManager.getApplication().runReadAction(Computable(f))
} }
fun schedule(f: () -> Unit, component: Component) {
return ApplicationManager.getApplication().invokeLater( { f() }, ModalityState.stateForComponent(component))
}
fun schedule(f: () -> Unit) {
return ApplicationManager.getApplication().invokeLater { f() }
}
} }
@@ -5,33 +5,22 @@
package org.jetbrains.kotlin.idea.debugger.coroutines.util package org.jetbrains.kotlin.idea.debugger.coroutines.util
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.debugger.ui.impl.watch.MethodsTracker import com.intellij.debugger.ui.impl.watch.MethodsTracker
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Computable import com.intellij.openapi.ui.MessageType
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.xdebugger.XDebuggerUtil import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.XSourcePosition import com.intellij.xdebugger.XSourcePosition
import com.sun.jdi.ClassType import com.intellij.xdebugger.impl.XDebuggerManagerImpl
import javaslang.control.Either
import org.jetbrains.kotlin.idea.debugger.coroutines.data.AsyncStackFrameDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SuspendStackFrameDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ApplicationThreadExecutor import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ApplicationThreadExecutor
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.LookupContinuation
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSourcePosition? { fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSourcePosition? {
val psiFacade = JavaPsiFacade.getInstance(project) val psiFacade = JavaPsiFacade.getInstance(project)
val psiClass = ApplicationThreadExecutor().invoke { val psiClass = ApplicationThreadExecutor().readAction {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
psiFacade.findClass( psiFacade.findClass(
stackTraceElement.className.substringBefore("$"), // find outer class, for which psi exists TODO stackTraceElement.className.substringBefore("$"), // find outer class, for which psi exists TODO
@@ -44,64 +33,10 @@ fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSource
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber) return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
} }
/*
private fun buildSuspendStackFrameChildren(descriptor: SuspendStackFrameDescriptor, frame: StackTraceElement, project: Project) {
val context = DebuggerManagerEx.getInstanceEx(project).context
val pos = getPosition(frame) ?: return
context.debugProcess?.managerThread?.schedule(object : SuspendContextCommandImpl(context.suspendContext) {
override fun contextAction() {
val (stack, stackFrame) = createSyntheticStackFrame(descriptor, pos) ?: return
val action: () -> Unit = { context.debuggerSession?.xDebugSession?.setCurrentStackFrame(stack, stackFrame) }
ApplicationManager.getApplication()
.invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
}
})
}
private fun buildAsyncStackFrameChildren(descriptor: AsyncStackFrameDescriptor, process: DebugProcessImpl) {
process.managerThread?.schedule(object : DebuggerCommandImpl() {
override fun action() {
val context = DebuggerManagerEx.getInstanceEx(project).context
val proxy = ThreadReferenceProxyImpl(
process.virtualMachineProxy,
descriptor.state.thread // is not null because it's a running coroutine
)
val executionStack = JavaExecutionStack(proxy, process, false)
executionStack.initTopFrame()
val frame = descriptor.frame.createFrame(process)
DebuggerUIUtil.invokeLater {
context.debuggerSession?.xDebugSession?.setCurrentStackFrame(
executionStack,
frame
)
}
}
})
}
private fun buildEmptyStackFrameChildren(descriptor: EmptyStackFrameDescriptor, project: Project) {
val position = getPosition(descriptor.frame) ?: return
val context = DebuggerManagerEx.getInstanceEx(project).context
val suspendContext = context.suspendContext ?: return
val proxy = suspendContext.thread ?: return
context.debugProcess?.managerThread?.schedule(object : DebuggerCommandImpl() {
override fun action() {
val executionStack =
JavaExecutionStack(proxy, context.debugProcess!!, false)
executionStack.initTopFrame()
val frame = SyntheticStackFrame(descriptor, emptyList(), position)
val action: () -> Unit =
{ context.debuggerSession?.xDebugSession?.setCurrentStackFrame(executionStack, frame) }
ApplicationManager.getApplication()
.invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
}
})
}
*/
class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) : class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
StackFrameDescriptorImpl(proxy, MethodsTracker()) { StackFrameDescriptorImpl(proxy, MethodsTracker())
class ProjectNotification(val project: Project) {
fun error(message: String) =
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project)
} }
@@ -5,11 +5,13 @@
package org.jetbrains.kotlin.idea.debugger.coroutines.view package org.jetbrains.kotlin.idea.debugger.coroutines.view
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.xdebugger.XDebugSession import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebugSessionListener import com.intellij.xdebugger.XDebugSessionListener
import com.intellij.xdebugger.frame.XSuspendContext import com.intellij.xdebugger.frame.XSuspendContext
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.sun.jdi.request.EventRequest
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
class CoroutineViewDebugSessionListener( class CoroutineViewDebugSessionListener(
@@ -19,48 +21,56 @@ class CoroutineViewDebugSessionListener(
val log by logger val log by logger
override fun sessionPaused() { override fun sessionPaused() {
log.info("XListener: sessionPaused")
val suspendContext = session.suspendContext ?: return requestClear() val suspendContext = session.suspendContext ?: return requestClear()
xCoroutineView.forceClear() xCoroutineView.alarm.cancel()
renew(suspendContext) renew(suspendContext)
} }
override fun sessionResumed() { override fun sessionResumed() {
xCoroutineView.saveState()
log.info("XListener: sessionResumed")
val suspendContext = session.suspendContext ?: return requestClear() val suspendContext = session.suspendContext ?: return requestClear()
log.warn("sessionResumed ${session}")
renew(suspendContext) renew(suspendContext)
} }
override fun sessionStopped() { override fun sessionStopped() {
log.info("XListener: sessionStopped")
val suspendContext = session.suspendContext ?: return requestClear() val suspendContext = session.suspendContext ?: return requestClear()
log.warn("sessionStopped ${session}")
renew(suspendContext) renew(suspendContext)
} }
override fun stackFrameChanged() { override fun stackFrameChanged() {
val suspendContext = session.suspendContext ?: return requestClear() xCoroutineView.saveState()
log.warn("stackFrameChanged ${session}") log.info("XListener: stackFrameChanged")
renew(suspendContext) // val suspendContext = session.suspendContext ?: return requestClear()
// log.warn("stackFrameChanged ${session}")
// renew(suspendContext)
} }
override fun beforeSessionResume() { override fun beforeSessionResume() {
log.info("XListener: beforeSessionResume")
log.warn("beforeSessionResume ${session}") log.warn("beforeSessionResume ${session}")
} }
override fun settingsChanged() { override fun settingsChanged() {
log.info("XListener: settingsChanged")
val suspendContext = session.suspendContext ?: return requestClear() val suspendContext = session.suspendContext ?: return requestClear()
log.warn("settingsChanged ${session}") log.warn("settingsChanged ${session}")
renew(suspendContext) renew(suspendContext)
} }
fun renew(suspendContext: XSuspendContext) { fun renew(suspendContext: XSuspendContext) {
DebuggerUIUtil.invokeLater { if(suspendContext is SuspendContextImpl && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
xCoroutineView.panel.tree.setRoot(xCoroutineView.createRoot(suspendContext), false) DebuggerUIUtil.invokeLater {
xCoroutineView.renewRoot(suspendContext)
}
} }
} }
fun requestClear() { private fun requestClear() {
if (ApplicationManager.getApplication().isUnitTestMode) { // no delay in tests if (ApplicationManager.getApplication().isUnitTestMode) { // no delay in tests
xCoroutineView.clear() xCoroutineView.resetRoot()
} else { } else {
xCoroutineView.alarm.cancelAndRequest() xCoroutineView.alarm.cancelAndRequest()
} }
@@ -28,9 +28,8 @@ import java.util.*
/** /**
* Tree of coroutines for [CoroutinesPanel] * Tree of coroutines for [CoroutinesPanel]
*/ */
@Deprecated("moved to XCoroutineView")
class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) { class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
private val log by logger
// called on every step/frame // called on every step/frame
override fun build(context: DebuggerContextImpl) { override fun build(context: DebuggerContextImpl) {
val session = context.debuggerSession val session = context.debuggerSession
@@ -38,9 +37,11 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
val command = RefreshCoroutinesTreeCommand(context, this) val command = RefreshCoroutinesTreeCommand(context, this)
val debuggerSessionState = session?.state ?: DebuggerSession.State.DISPOSED val debuggerSessionState = session?.state ?: DebuggerSession.State.DISPOSED
if (ApplicationManager.getApplication().isUnitTestMode || debuggerSessionState in EnumSet.of(DebuggerSession.State.PAUSED, DebuggerSession.State.RUNNING)) { if (ApplicationManager.getApplication().isUnitTestMode || debuggerSessionState in EnumSet
.of(DebuggerSession.State.PAUSED, DebuggerSession.State.RUNNING)
) {
showMessage(MessageDescriptor.EVALUATING) showMessage(MessageDescriptor.EVALUATING)
ManagerThreadExecutor(context.debugProcess!!).schedule(command) context.debugProcess!!.managerThread.schedule(command)
} else { } else {
showMessage(session?.stateDescription ?: DebuggerBundle.message("status.debug.stopped")) showMessage(session?.stateDescription ?: DebuggerBundle.message("status.debug.stopped"))
} }
@@ -48,7 +49,7 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
override fun getBuildNodeCommand(node: DebuggerTreeNodeImpl): DebuggerCommandImpl? { override fun getBuildNodeCommand(node: DebuggerTreeNodeImpl): DebuggerCommandImpl? {
return when(val descriptor = node.descriptor) { return when (val descriptor = node.descriptor) {
is CoroutineDescriptorImpl -> is CoroutineDescriptorImpl ->
CoroutineBuildFrameCommand(node, descriptor, myNodeManager, debuggerContext) CoroutineBuildFrameCommand(node, descriptor, myNodeManager, debuggerContext)
is CreationFramesDescriptor -> is CreationFramesDescriptor ->
@@ -71,7 +72,8 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
} }
} }
class CoroutineInfoCache(val cache: MutableList<CoroutineInfoData> = mutableListOf(), var state: CacheState = CacheState.INIT class CoroutineInfoCache(
val cache: MutableList<CoroutineInfoData> = mutableListOf(), var state: CacheState = CacheState.INIT
) { ) {
fun ok(infoList: List<CoroutineInfoData>) { fun ok(infoList: List<CoroutineInfoData>) {
cache.clear() cache.clear()
@@ -84,11 +86,11 @@ class CoroutineInfoCache(val cache: MutableList<CoroutineInfoData> = mutableList
state = CacheState.FAIL state = CacheState.FAIL
} }
fun isOk() : Boolean { fun isOk(): Boolean {
return state == CacheState.OK return state == CacheState.OK
} }
} }
enum class CacheState() { enum class CacheState {
OK,FAIL,INIT OK, FAIL, INIT
} }
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerActions
/** /**
* Added into ui in [CoroutineProjectConnectionListener.registerCoroutinesPanel] * Added into ui in [CoroutineProjectConnectionListener.registerCoroutinesPanel]
*/ */
@Deprecated("moved to XCoroutineView")
class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : ThreadsPanel(project, stateManager) { class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : ThreadsPanel(project, stateManager) {
override fun createTreeView(): DebuggerTree { override fun createTreeView(): DebuggerTree {
@@ -0,0 +1,177 @@
/*
* 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.coroutines.view
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.settings.ThreadsViewSettings
import com.intellij.icons.AllIcons
import com.intellij.openapi.editor.HighlighterColors
import com.intellij.openapi.editor.colors.CodeInsightColors
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.ui.ColoredTextContainer
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleTextAttributes
import com.intellij.xdebugger.frame.presentation.XValuePresentation
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
import com.sun.jdi.Location
import com.sun.jdi.ReferenceType
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
import javax.swing.Icon
class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
val texts = mutableListOf<String>()
val textKeyAttributes = mutableListOf<TextAttributesKey>()
constructor(icon: Icon?, hasChildrens: Boolean, text: String) : this(icon, hasChildrens) {
append(text)
}
internal fun append(value: String) {
texts.add(value)
textKeyAttributes.add(CoroutineDebuggerColors.REGULAR_ATTRIBUTES)
}
internal fun appendValue(value: String) {
texts.add(value)
textKeyAttributes.add(CoroutineDebuggerColors.VALUE_ATTRIBUTES)
}
fun appendToComponent(component: ColoredTextContainer) {
val size: Int = texts.size
for (i in 0 until size) {
val text: String = texts.get(i)
val attribute: TextAttributesKey = textKeyAttributes.get(i)
component.append(text, when(attribute) {
CoroutineDebuggerColors.REGULAR_ATTRIBUTES -> SimpleTextAttributes.REGULAR_ATTRIBUTES
CoroutineDebuggerColors.VALUE_ATTRIBUTES -> XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
})
}
}
fun forEachTextBlock(f: (Pair<String, TextAttributesKey>) -> Unit) {
for (pair in texts zip textKeyAttributes)
f(pair)
}
fun simpleString(): String {
val component = SimpleColoredComponent()
appendToComponent(component)
return component.getCharSequence(false).toString()
}
fun valuePresentation(): XValuePresentation {
return object : XValuePresentation() {
override fun isShowName() = false
override fun getSeparator() = ""
override fun renderValue(renderer: XValueTextRenderer) {
forEachTextBlock {
renderer.renderValue(it.first, it.second)
}
}
}
}
}
interface CoroutineDebuggerColors {
companion object {
val REGULAR_ATTRIBUTES =
TextAttributesKey.createTextAttributesKey("REGULAR_ATTRIBUTES", HighlighterColors.NO_HIGHLIGHTING)
val VALUE_ATTRIBUTES =
TextAttributesKey.createTextAttributesKey("VALUE_ATTRIBUTES", CodeInsightColors.WARNINGS_ATTRIBUTES)
}
}
class SimpleColoredTextIconPresentationRenderer {
private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance()
fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon {
val thread = infoData.activeThread
val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: ""
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
val icon = when (infoData.state) {
CoroutineInfoData.State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended
CoroutineInfoData.State.RUNNING -> AllIcons.Debugger.ThreadRunning
CoroutineInfoData.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
}
val label = SimpleColoredTextIcon(icon, true)
label.append("\"")
label.appendValue(infoData.name)
label.append("\": ${infoData.state}")
if(name.isNotEmpty()) {
label.append(" on thread \"")
label.appendValue(name)
label.append("\": $threadState")
}
return label
}
/**
* Taken from #StackFrameDescriptorImpl.calcRepresentation
*/
fun render(location: Location): SimpleColoredTextIcon {
val label = SimpleColoredTextIcon(null, false)
DebuggerUIUtil.getColorScheme(null)
if (location.method() != null) {
val myName = location.method().name()
val methodDisplay = if (settings.SHOW_ARGUMENTS_TYPES)
DebuggerUtilsEx.methodNameWithArguments(location.method())
else
myName
label.appendValue(methodDisplay)
}
if (settings.SHOW_LINE_NUMBER) {
label.append(":")
label.append("" + DebuggerUtilsEx.getLineNumber(location, false))
}
if (settings.SHOW_CLASS_NAME) {
val name: String?
name = try {
val refType: ReferenceType = location.declaringType()
refType.name()
} catch (e: InternalError) {
e.toString()
}
if (name != null) {
label.append(", ")
val dotIndex = name.lastIndexOf('.')
if (dotIndex < 0) {
label.append(name)
} else {
label.append(name.substring(dotIndex + 1))
if (settings.SHOW_PACKAGE_NAME) {
label.append(" (${name.substring( 0, dotIndex)})")
}
}
}
}
if (settings.SHOW_SOURCE_NAME) {
label.append(", ")
val sourceName = DebuggerUtilsEx.getSourceName(location) { e: Throwable? -> "Unknown Source" }
label.append(sourceName)
}
return label
}
fun renderCreationNode(infoData: CoroutineInfoData) =
SimpleColoredTextIcon(AllIcons.Debugger.ThreadSuspended, true, "Creation stack frame of ${infoData.name}")
fun renderErrorNode(error: String) =
SimpleColoredTextIcon(AllIcons.Actions.Lightning,false, error)
fun renderRoorNode(text: String) =
SimpleColoredTextIcon(null, true, text)
fun renderGroup(groupName: String) =
SimpleColoredTextIcon(AllIcons.Debugger.ThreadGroup,true, groupName)
}
@@ -5,54 +5,66 @@
package org.jetbrains.kotlin.idea.debugger.coroutines.view package org.jetbrains.kotlin.idea.debugger.coroutines.view
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaDebugProcess import com.intellij.debugger.engine.JavaDebugProcess
import com.intellij.debugger.engine.JavaExecutionStack
import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.debugger.settings.ThreadsViewSettings
import com.intellij.icons.AllIcons
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.ui.DoubleClickListener
import com.intellij.ui.OnePixelSplitter import com.intellij.ui.OnePixelSplitter
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleColoredText
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.SingleAlarm import com.intellij.util.SingleAlarm
import com.intellij.xdebugger.XDebugSession import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.XSourcePosition import com.intellij.xdebugger.XSourcePosition
import com.intellij.xdebugger.frame.* import com.intellij.xdebugger.frame.*
import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation
import com.intellij.xdebugger.frame.presentation.XValuePresentation
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeRestorer
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
import com.sun.jdi.Location import com.sun.jdi.ClassType
import com.sun.jdi.ReferenceType import javaslang.control.Either
import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineAsyncStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerContentInfo import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerContentInfo
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineStackFrameItem import org.jetbrains.kotlin.idea.debugger.coroutines.command.*
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CreationCoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutines.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SuspendStackFrameDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutines.data.SyntheticStackFrame
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ApplicationThreadExecutor
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.AsyncStackTraceContext
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ManagerThreadExecutor import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.ManagerThreadExecutor
import org.jetbrains.kotlin.idea.debugger.coroutines.util.CreateContentParams import org.jetbrains.kotlin.idea.debugger.coroutines.util.*
import org.jetbrains.kotlin.idea.debugger.coroutines.util.CreateContentParamsProvider import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.coroutines.util.XDebugSessionListenerProvider import java.awt.event.KeyAdapter
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger import java.awt.event.KeyEvent
import javax.swing.Icon import java.awt.event.MouseEvent
class XCoroutineView(val project: Project, val session: XDebugSession) : class XCoroutineView(val project: Project, val session: XDebugSession) :
Disposable, XDebugSessionListenerProvider, CreateContentParamsProvider { Disposable, XDebugSessionListenerProvider, CreateContentParamsProvider {
private var needToRestoreState: Boolean = false
val log by logger val log by logger
val splitter = OnePixelSplitter("SomeKey", 0.25f) val splitter = OnePixelSplitter("SomeKey", 0.25f)
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null) val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
val alarm = SingleAlarm(Runnable { clear() }, VIEW_CLEAR_DELAY, this) val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
val debugProcess: DebugProcessImpl = (session.debugProcess as JavaDebugProcess).debuggerSession.process val javaDebugProcess = session.debugProcess as JavaDebugProcess
val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
val renderer = SimpleColoredTextIconPresentationRenderer()
val managerThreadExecutor = ManagerThreadExecutor(debugProcess)
val applicationThreadExecutor = ApplicationThreadExecutor()
var treeState: XDebuggerTreeState? = null
private var restorer: XDebuggerTreeRestorer? = null
private var selectedNodeListener = XDebuggerTreeSelectedNodeListener(panel.tree)
companion object { companion object {
private val VIEW_CLEAR_DELAY = 100 //ms private val VIEW_CLEAR_DELAY = 100 //ms
@@ -60,26 +72,42 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
init { init {
splitter.firstComponent = panel.mainPanel splitter.firstComponent = panel.mainPanel
selectedNodeListener.installOn()
} }
fun clear() {
fun saveState() {
DebuggerUIUtil.invokeLater { DebuggerUIUtil.invokeLater {
panel.tree if (! (panel.tree.root is EmptyNode)) {
.setRoot(object : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {}) {}, false) treeState = XDebuggerTreeState.saveState(panel.tree)
log.info("Tree state saved")
}
}
}
fun resetRoot() {
DebuggerUIUtil.invokeLater {
panel.tree.setRoot(EmptyNode(), false)
}
}
fun renewRoot(suspendContext: XSuspendContext) {
panel.tree.setRoot(XCoroutinesRootNode(suspendContext), false)
if(treeState != null) {
restorer?.dispose()
restorer = treeState?.restoreState(panel.tree)
log.info("Tree state restored")
} }
} }
override fun dispose() { override fun dispose() {
restorer?.dispose()
} }
fun forceClear() { fun forceClear() {
alarm.cancel() alarm.cancel()
clear()
} }
fun createRoot(suspendContext: XSuspendContext) =
XCoroutinesRootNode(panel.tree, suspendContext)
override fun debugSessionListener(session: XDebugSession) = override fun debugSessionListener(session: XDebugSession) =
CoroutineViewDebugSessionListener(session, this) CoroutineViewDebugSessionListener(session, this)
@@ -92,172 +120,211 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
panel.tree panel.tree
) )
} inner class EmptyNode : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {})
class XCoroutinesRootNode(tree: XDebuggerTree, suspendContext: XSuspendContext) : inner class XCoroutinesRootNode(suspendContext: XSuspendContext) :
XValueContainerNode<CoroutineGroupContainer>(tree, null, false, CoroutineGroupContainer(suspendContext)) { XValueContainerNode<CoroutineGroupContainer>(panel.tree, null, false, CoroutineGroupContainer(suspendContext, "Default group"))
}
class CoroutineGroupContainer( inner class CoroutineGroupContainer(val suspendContext: XSuspendContext, val groupName: String) : XValueContainer() {
val suspendContext: XSuspendContext override fun computeChildren(node: XCompositeNode) {
) : XValueContainer() { val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
override fun computeChildren(node: XCompositeNode) { node.addChildren(groups, true)
val children = XValueChildrenList()
children.add("", CoroutineContainer(suspendContext as SuspendContextImpl, "Default group"))
node.addChildren(children, true)
}
}
class CoroutineContainer(
val suspendContext: SuspendContextImpl,
val groupName: String
) : XValue() {
override fun computeChildren(node: XCompositeNode) {
val managerThreadExecutor = ManagerThreadExecutor(suspendContext.debugProcess)
managerThreadExecutor.schedule {
val debugProbesProxy = CoroutinesDebugProbesProxy(suspendContext)
var coroutineCache = debugProbesProxy.dumpCoroutines()
if(coroutineCache.isOk()) {
val children = XValueChildrenList()
coroutineCache.cache.forEach {
children.add("", FramesContainer(it, debugProbesProxy, managerThreadExecutor))
}
node.addChildren(children, true)
} else
node.addChildren(XValueChildrenList.EMPTY, true)
} }
} }
override fun computePresentation(node: XValueNode, place: XValuePlace) { inner class CoroutineContainer(
node.setPresentation(AllIcons.Debugger.ThreadGroup, XRegularValuePresentation(groupName, null, ""), true) val suspendContext: XSuspendContext,
} val groupName: String
} ) : RendererContainer(renderer.renderGroup(groupName)) {
class FramesContainer( override fun computeChildren(node: XCompositeNode) {
private val coroutineInfoData: CoroutineInfoData, managerThreadExecutor.on(suspendContext).schedule {
private val debugProbesProxy: CoroutinesDebugProbesProxy, val debugProbesProxy = CoroutinesDebugProbesProxy(suspendContext)
private val managerThreadExecutor: ManagerThreadExecutor
) : XValue() { var coroutineCache = debugProbesProxy.dumpCoroutines()
override fun computeChildren(node: XCompositeNode) { if (coroutineCache.isOk()) {
managerThreadExecutor.schedule { val children = XValueChildrenList()
val children = XValueChildrenList() coroutineCache.cache.forEach {
debugProbesProxy.frameBuilder().build(coroutineInfoData) children.add(FramesContainer(it, suspendContext))
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
coroutineInfoData.stackFrameList.forEach {
val frameValue = when (it) {
is CreationCoroutineStackFrameItem -> {
creationStack.add(it)
null
} }
else -> CoroutineFrameValue(it) node.addChildren(children, true)
} } else {
frameValue?.let { node.addChildren(XValueChildrenList.singleton(ErrorNode("Error occured while fetching information")), true)
children.add("", frameValue)
} }
} }
children.add("", CreationFramesContainer(creationStack)) }
}
inner class ErrorNode(val error: String) : RendererContainer(renderer.renderErrorNode(error))
inner class FramesContainer(
private val infoData: CoroutineInfoData,
private val suspendContext: XSuspendContext
) : RendererContainer(renderer.render(infoData)) {
override fun computeChildren(node: XCompositeNode) {
managerThreadExecutor.on(suspendContext).schedule {
val debugProbesProxy = CoroutinesDebugProbesProxy(suspendContext)
val children = XValueChildrenList()
debugProbesProxy.frameBuilder().build(infoData)
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
infoData.stackFrameList.forEach {
if (it is CreationCoroutineStackFrameItem)
creationStack.add(it)
else
children.add(CoroutineFrameValue(it))
}
children.add(CreationFramesContainer(infoData, creationStack))
node.addChildren(children, true)
}
}
}
inner class CreationFramesContainer(
private val infoData: CoroutineInfoData,
private val creationFrames: List<CreationCoroutineStackFrameItem>
) : RendererContainer(renderer.renderCreationNode(infoData)) {
override fun computeChildren(node: XCompositeNode) {
val children = XValueChildrenList()
creationFrames.forEach {
children.add(CoroutineFrameValue(it))
}
node.addChildren(children, true) node.addChildren(children, true)
} }
} }
override fun computePresentation(node: XValueNode, place: XValuePlace) { inner class CoroutineFrameValue(val frame: CoroutineStackFrameItem
val icon = when (coroutineInfoData.state) { ) : XNamedValue(frame.uniqueId()) {
CoroutineInfoData.State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended override fun computePresentation(node: XValueNode, place: XValuePlace) =
CoroutineInfoData.State.RUNNING -> AllIcons.Debugger.ThreadRunning applyRenderer(node, renderer.render(frame.location()))
CoroutineInfoData.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle }
private fun applyRenderer(node: XValueNode, presentation: SimpleColoredTextIcon) =
node.setPresentation(presentation.icon, presentation.valuePresentation(), presentation.hasChildrens)
open inner class RendererContainer(val presentation: SimpleColoredTextIcon) : XNamedValue(presentation.simpleString()) {
override fun computePresentation(node: XValueNode, place: XValuePlace) =
applyRenderer(node, presentation)
}
inner class XDebuggerTreeSelectedNodeListener(val tree: XDebuggerTree) {
fun installOn() {
object : DoubleClickListener() {
override fun onDoubleClick(e: MouseEvent) =
nodeSelected(Either.left(e))
}.installOn(tree)
tree.addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
val key = e.keyCode
if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT)
nodeSelected(Either.right(e))
}
})
} }
val valuePresentation = customizePresentation(coroutineInfoData) fun nodeSelected(event: Either<MouseEvent, KeyEvent>) : Boolean {
node.setPresentation(icon, valuePresentation, true) val selectedNodes = tree.getSelectedNodes(XValueNodeImpl::class.java, null)
} if (selectedNodes.size == 1) {
val node = selectedNodes[0]
val valueContainer = node.valueContainer
if (valueContainer is XCoroutineView.CoroutineFrameValue) {
val frame = valueContainer.frame
val threadSuspendContext = session.suspendContext as SuspendContextImpl
when (frame) {
is RunningCoroutineStackFrameItem -> {
val threadProxy = valueContainer.frame.frame.threadProxy()
val isCurrentContext = threadSuspendContext.thread == threadProxy
createStackAndSetFrame(threadProxy, { frame.stackFrame }, isCurrentContext)
}
is CreationCoroutineStackFrameItem -> {
val position = getPosition(frame.stackTraceElement) ?: return false
val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
val stackFrame =
createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) })
}
is SuspendCoroutineStackFrameItem -> {
val position = getPosition(frame.stackTraceElement) ?: return false
}
is AsyncCoroutineStackFrameItem -> {
private fun customizePresentation(coroutineInfoData: CoroutineInfoData): XRegularValuePresentation { }
val component = SimpleColoredComponent() // else -> {
val thread = coroutineInfoData.thread // val (stack, stackFrame) = createSyntheticStackFrame(descriptor, pos) ?: return
val name = thread?.name()?.substringBefore(" @${coroutineInfoData.name}") ?: "" // val action: () -> Unit = { context.debuggerSession?.xDebugSession?.setCurrentStackFrame(stack, stackFrame) }
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else "" // - ApplicationManager.getApplication()
// - .invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
component.append("\"").append(coroutineInfoData.name, XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES) // }
component.append("\": ${coroutineInfoData.state} ${if (name.isNotEmpty()) "on thread \"$name\":$threadState" else ""}") }
return XRegularValuePresentation(component.getCharSequence(false).toString(), null, "")
}
}
class CreationFramesContainer(val creationFrames: List<CreationCoroutineStackFrameItem>) : XValue() {
override fun computeChildren(node: XCompositeNode) {
val children = XValueChildrenList()
creationFrames.forEach {
children.add("", CoroutineFrameValue(it))
}
node.addChildren(children, true)
}
override fun computePresentation(node: XValueNode, place: XValuePlace) {
node.setPresentation(AllIcons.Debugger.ThreadSuspended, XRegularValuePresentation("Creation stack frame", null, ""), true)
}
}
class CoroutineFrameValue(val frame: CoroutineStackFrameItem) : XValue() {
override fun computePresentation(node: XValueNode, place: XValuePlace) {
val presentation = customizePresentation(frame)
if(node is XValueNodeImpl) {
node.setPresentation(null, XRegularValuePresentation("", null, ""), false)
presentation.texts.forEachIndexed { i, s ->
node.text.append(s, presentation.attributes[i])
}
} else {
val component = SimpleColoredComponent()
presentation.appendToComponent(component)
val valuePresentation = XRegularValuePresentation(component.getCharSequence(false).toString(), null, "")
node.setPresentation(null, valuePresentation, false)
}
}
}
fun customizePresentation(frame: CoroutineStackFrameItem) : SimpleColoredText =
calcRepresentation(frame.location(), ThreadsViewSettings.getInstance())
/**
* Taken from #StackFrameDescriptorImpl.calcRepresentation
*/
fun calcRepresentation(location: Location, settings: ThreadsViewSettings): SimpleColoredText {
val label = SimpleColoredText()
DebuggerUIUtil.getColorScheme(null)
if (location.method() != null) {
val myName = location.method().name()
label.append(if (settings.SHOW_ARGUMENTS_TYPES) DebuggerUtilsEx.methodNameWithArguments(location.method()) else myName, XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES)
}
if (settings.SHOW_LINE_NUMBER) {
label.append(":", SimpleTextAttributes.REGULAR_ATTRIBUTES)
label.append("" + DebuggerUtilsEx.getLineNumber(location, false), SimpleTextAttributes.REGULAR_ATTRIBUTES)
}
if (settings.SHOW_CLASS_NAME) {
val name: String?
name = try {
val refType: ReferenceType = location.declaringType()
refType?.name()
} catch (e: InternalError) {
e.toString()
}
if (name != null) {
label.append(", ", SimpleTextAttributes.REGULAR_ATTRIBUTES)
val dotIndex = name.lastIndexOf('.')
if (dotIndex < 0) {
label.append(name, SimpleTextAttributes.REGULAR_ATTRIBUTES)
} else {
label.append(name.substring(dotIndex + 1), SimpleTextAttributes.REGULAR_ATTRIBUTES)
if (settings.SHOW_PACKAGE_NAME) {
label.append(" (${name.substring( 0, dotIndex)})", SimpleTextAttributes.REGULAR_ATTRIBUTES)
} }
} }
return false
} }
} }
if (settings.SHOW_SOURCE_NAME) {
label.append(", ", SimpleTextAttributes.REGULAR_ATTRIBUTES) fun createSyntheticStackFrame() {}
label.append(DebuggerUtilsEx.getSourceName(location) { e: Throwable? -> "Unknown Source" }, SimpleTextAttributes.REGULAR_ATTRIBUTES)
fun createStackAndSetFrame(threadReferenceProxy: ThreadReferenceProxyImpl, stackFrameProvider: () -> XStackFrame, isCurrentContext: Boolean = false) {
val threadSuspendContext = session.suspendContext as SuspendContextImpl
managerThreadExecutor.on(threadSuspendContext).schedule {
val stackFrame = stackFrameProvider.invoke()
val executionStack = createExecutionStack(threadReferenceProxy, isCurrentContext)
applicationThreadExecutor.schedule(
{
session.setCurrentStackFrame(executionStack, stackFrame)
}, panel.tree)
}
}
fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false) : XExecutionStack {
val executionStack = CoroutineDebuggerExecutionStack(proxy, isCurrentContext)
executionStack.initTopFrame()
return executionStack
}
inner class CoroutineDebuggerExecutionStack(threadReferenceProxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean) :
JavaExecutionStack(threadReferenceProxy, debugProcess, isCurrentContext)
private fun getPosition(frame: StackTraceElement): XSourcePosition? {
val psiFacade = JavaPsiFacade.getInstance(project)
val psiClass = psiFacade.findClass(
frame.className.substringBefore("$"), // find outer class, for which psi exists TODO
GlobalSearchScope.everythingScope(project)
)
val classFile = psiClass?.containingFile?.virtualFile
// to convert to 0-based line number or '-1' to do not move
val lineNumber = if (frame.lineNumber > 0) frame.lineNumber - 1 else return null
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
}
private fun createSyntheticStackFrame(
descriptor: SuspendStackFrameDescriptor,
pos: XSourcePosition
): Pair<XExecutionStack, SyntheticStackFrame>? {
val context = DebuggerManagerEx.getInstanceEx(project).context
val suspendContext = context.suspendContext ?: return null
val proxy = suspendContext.thread ?: return null
val executionStack = JavaExecutionStack(proxy, suspendContext.debugProcess, false)
executionStack.initTopFrame()
val evalContext = context.createEvaluationContext()
val frameProxy = evalContext?.frameProxy ?: return null
val execContext = ExecutionContext(evalContext, frameProxy)
val continuation = descriptor.continuation // guaranteed that it is a BaseContinuationImpl
val aMethod = (continuation.type() as ClassType).concreteMethodByName(
"getStackTraceElement",
"()Ljava/lang/StackTraceElement;"
)
val vars = with(CoroutineAsyncStackTraceProvider()) {
AsyncStackTraceContext(
execContext,
aMethod
).getSpilledVariables(continuation)
} ?: return null
return executionStack to SyntheticStackFrame(descriptor, vars, pos)
} }
return label
} }