[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.evaluation.EvaluationContextImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.xdebugger.frame.XSuspendContext
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.*
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
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
@@ -21,7 +22,12 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
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 location = frameProxy.location()
@@ -31,7 +37,7 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
val method = location.safeMethod() ?: return defaultResult
val threadReference = frameProxy.threadProxy().threadReference
if (threadReference == null || !threadReference.isSuspended || !(suspendContext.debugProcess as DebugProcessImpl).canRunEvaluation)
if (threadReference == null || !threadReference.isSuspended || !canRunEvaluation(suspendContext))
return defaultResult
@@ -41,7 +47,7 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
private fun createAsyncStackTraceContext(
frameProxy: StackFrameProxyImpl,
suspendContext: SuspendContext,
suspendContext: XSuspendContext,
method: Method
): AsyncStackTraceContext {
val evaluationContext = EvaluationContextImpl(suspendContext as SuspendContextImpl, frameProxy)
@@ -49,5 +55,8 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
// DebugMetadataKt not found, probably old kotlin-stdlib version
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.NodeManagerImpl
@Deprecated("moved to XCoroutineView")
open class BuildCoroutineNodeCommand(
val node: DebuggerTreeNodeImpl,
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.CoroutineCreatedStackFrameDescriptor
@Deprecated("moved to XCoroutineView")
class CoroutineBuildCreationFrameCommand(
node: DebuggerTreeNodeImpl,
val descriptor: CreationFramesDescriptor,
@@ -21,10 +22,11 @@ class CoroutineBuildCreationFrameCommand(
val threadProxy = debuggerContext.suspendContext?.thread ?: return
val evalContext = debuggerContext.createEvaluationContext() ?: return
val proxy = threadProxy.forceFrames().first()
descriptor.frames.forEach {
for(it in descriptor.frames) {
val descriptor = myNodeManager.createNode(
CoroutineCreatedStackFrameDescriptor(it, proxy), evalContext)
myChildren.add(descriptor)
}
updateUI(true)
}
@@ -89,13 +89,13 @@ class CoroutineBuildFrameCommand(
debugProcess: DebugProcessImpl,
evalContext: EvaluationContextImpl
): Boolean {
if (descriptor.infoData.thread == null) {
if (descriptor.infoData.activeThread == null) {
myChildren.add(myNodeManager.createMessageNode("Frames are not available"))
return true
}
val proxy = ThreadReferenceProxyImpl(
debugProcess.virtualMachineProxy,
descriptor.infoData.thread
descriptor.infoData.activeThread
)
val frames = proxy.forceFrames()
var endRange = findResumeWithMethodFrameIndex(frames)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.debugger.coroutines.command
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.*
import com.intellij.debugger.jdi.ClassesByNameProvider
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.ui.impl.watch.MethodsTracker
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.openapi.project.Project
import com.intellij.util.containers.ContainerUtil
import com.intellij.xdebugger.XSourcePosition
import com.intellij.xdebugger.frame.XExecutionStack
import com.intellij.xdebugger.frame.XStackFrame
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.Location
import com.sun.jdi.ReferenceType
import com.sun.jdi.ThreadReference
import com.intellij.xdebugger.frame.XSuspendContext
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineAsyncStackTraceProvider
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 coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
val virtualMachineProxy = suspendContext.debugProcess.virtualMachineProxy
val debugProcess = (suspendContext as SuspendContextImpl).debugProcess
val virtualMachineProxy = debugProcess.virtualMachineProxy
val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses())
companion object {
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 suspendedThreadProxy = suspendedThreadProxy()
val firstSuspendedStackFrameProxyImpl = firstSuspendedThreadFrame()
val creationFrameSeparatorIndex = findCreationFrameIndex(ci.stackTrace)
val runningThreadReferenceProxyImpl = currentRunningThreadProxy(ci.thread ?: suspendedThreadProxy().threadReference)
val positionManager = suspendContext.debugProcess.positionManager
val creationFrameSeparatorIndex = findCreationFrameIndex(coroutine.stackTrace)
val positionManager = debugProcess.positionManager
if (ci.state == CoroutineInfoData.State.RUNNING && runningThreadReferenceProxyImpl is ThreadReferenceProxyImpl) {
val executionStack = JavaExecutionStack(runningThreadReferenceProxyImpl, suspendContext.debugProcess, suspendContext.thread == runningThreadReferenceProxyImpl)
if (coroutine.state == CoroutineInfoData.State.RUNNING && coroutine.activeThread is ThreadReference) {
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)
for (frameIndex in 0..frames.lastIndex) {
val runningStackFrameProxy = frames[frameIndex]
@@ -55,47 +62,46 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
val previousJavaFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true)
val asyncStackTrace = coroutineStackFrameProvider
.getAsyncStackTrace(previousJavaFrame, suspendContext)
asyncStackTrace?.forEach {
// val xStackFrame = positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, it.location)
asyncStackTrace?.forEach { asyncFrame ->
val xStackFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true)
// val itStackFrame = null
coroutineStackFrameList.add(AsyncCoroutineStackFrameItem(runningStackFrameProxy, "some label", it, xStackFrame))
coroutineStackFrameList.add(AsyncCoroutineStackFrameItem(runningStackFrameProxy, asyncFrame, xStackFrame))
}
} else {
// val xStackFrame = stackFrame(positionManager, runningStackFrameProxy, runningStackFrameProxy.location())
val xStackFrame = executionStack.createStackFrame(runningStackFrameProxy)
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, "", xStackFrame))
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, xStackFrame))
}
}
} else if (ci.state == CoroutineInfoData.State
.SUSPENDED || runningThreadReferenceProxyImpl == null
) { // to get frames from CoroutineInfo anyway
} else if (coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) {
// to get frames from CoroutineInfo anyway
// the thread is paused on breakpoint - it has at least one frame
ci.stackTrace.subList(0, creationFrameSeparatorIndex).forEach {
val xStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, it)
// val xStackFrame = null
coroutineStackFrameList.add(SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, "suspended frame", it, xStackFrame))
val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex + 1)
for (suspendedFrame in suspendedStackTrace) {
val suspendedXStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, suspendedFrame)
coroutineStackFrameList.add(
SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame)
)
}
}
val executionStack = JavaExecutionStack(suspendedThreadProxy(), suspendContext.debugProcess, false)
val executionStack = JavaExecutionStack(suspendedThreadProxy(), debugProcess, false)
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)
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
}
fun createLocation(stackTraceElement: StackTraceElement): Location {
return findLocation(
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
stackTraceElement.methodName,
stackTraceElement.lineNumber
)
}
private fun suspendedSameThread(activeThread: ThreadReference) =
activeThread == suspendedThreadProxy().threadReference
private fun createLocation(stackTraceElement: StackTraceElement): Location = findLocation(
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
stackTraceElement.methodName,
stackTraceElement.lineNumber
)
private fun findLocation(
type: ReferenceType?,
@@ -113,17 +119,16 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
} 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 {
return positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, location)!!
return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!!
}
fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, stackTraceElement: StackTraceElement) : XStackFrame {
val location = createLocation(stackTraceElement)
// val sourcePosition = getPosition(stackTraceElement)
return positionManager.createStackFrame(runningStackFrameProxy, suspendContext.debugProcess, location)!!
return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!!
}
/**
@@ -143,28 +148,29 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
private fun firstSuspendedThreadFrame(): StackFrameProxyImpl =
suspendedThreadProxy().forceFrames().first()
// retrieves currently suspended but active and executing corouting thread proxy
fun currentRunningThreadProxy(threadReference: ThreadReference?): ThreadReferenceProxyImpl? =
ThreadReferenceProxyImpl(suspendContext.debugProcess.virtualMachineProxy, threadReference)
// retrieves currently suspended but active and executing coroutine thread proxy
fun runningThreadProxy(threadReference: ThreadReference) =
ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, threadReference)
// retrieves current suspended thread proxy
fun suspendedThreadProxy(): ThreadReferenceProxyImpl =
suspendContext.thread!! // @TODO hash replace !!
private fun suspendedThreadProxy(): ThreadReferenceProxyImpl =
(suspendContext as SuspendContextImpl).thread!! // @TODO hash replace !!
private fun findResumeMethodIndex(frames: List<StackFrameProxyImpl>): Int {
for (j: Int in frames.lastIndex downTo 0)
if (isResumeMethodFrame(frames[j])) {
return j
for (i: Int in frames.lastIndex downTo 0)
if (isResumeMethodFrame(frames[i])) {
return i
}
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
* This code was migrated from previous implementation, has to be refactored @TODO
*/
/*
private fun createSyntheticStackFrame(
descriptor: SuspendStackFrameDescriptor,
pos: XSourcePosition,
@@ -191,50 +197,59 @@ class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
} ?: return null
return executionStack to SyntheticStackFrame(descriptor, vars, pos)
}
*/
}
class CreationCoroutineStackFrameItem(
frame: StackFrameProxyImpl,
label: String = "",
val stackTraceElement: StackTraceElement,
stackFrame: XStackFrame,
val location: Location
) : CoroutineStackFrameItem(frame, label, stackFrame) {
) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() = location
fun emptyDescriptor() =
EmptyStackFrameDescriptor(stackTraceElement, frame)
}
class SuspendCoroutineStackFrameItem(
frame: StackFrameProxyImpl,
label: String = "",
val stackTraceElement: StackTraceElement,
stackFrame: XStackFrame
) : CoroutineStackFrameItem(frame, label, stackFrame) {
) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() = frame.location()
}
class AsyncCoroutineStackFrameItem(
frame: StackFrameProxyImpl,
label: String = "",
val frameItem: StackFrameItem,
stackFrame: XStackFrame
) : CoroutineStackFrameItem(frame, label, stackFrame) {
) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() : Location = frame.location()
}
class RunningCoroutineStackFrameItem(
frame: StackFrameProxyImpl,
label: String = "",
stackFrame: XStackFrame
) : CoroutineStackFrameItem(frame, label, stackFrame) {
) : CoroutineStackFrameItem(frame, stackFrame) {
val location = frame.location() // it should be invoked in manager thread
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
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.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.MessageType
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.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.CoroutinesDebugProbesProxy
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
@Suppress("ComponentNotRegistered")
class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
private val logger = Logger.getInstance(this::class.java)
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
@@ -42,20 +41,15 @@ class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
val process = context.debugProcess ?: return
process.managerThread.schedule(object : SuspendContextCommandImpl(context.suspendContext) {
override fun contextAction() {
val states = CoroutinesDebugProbesProxy(context.suspendContext!!).dumpCoroutines() ?: return
val states = CoroutinesDebugProbesProxy(context.suspendContext ?: return)
.dumpCoroutines()
if (states.isOk()) {
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(
KotlinBundle.message("debugger.session.tab.coroutine.message.error"),
MessageType.ERROR
).notify(project)
val message = KotlinBundle.message("debugger.session.tab.coroutine.message.error")
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message,MessageType.ERROR).notify(project)
} else {
val f = fun() {
addCoroutineDump(
project,
states.cache,
session.xDebugSession?.ui ?: return,
session.searchScope
)
val ui = session.xDebugSession?.ui ?: return
addCoroutineDump(project, states.cache, ui, session.searchScope)
}
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.ui.impl.watch.DebuggerTreeNodeImpl
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.debugger.coroutines.data.CoroutineDescriptorData
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.ManagerThreadExecutor
import org.jetbrains.kotlin.idea.debugger.coroutines.proxy.createEvaluationContext
import org.jetbrains.kotlin.idea.debugger.coroutines.util.ProjectNotification
import org.jetbrains.kotlin.idea.debugger.coroutines.view.CoroutinesDebuggerTree
@Deprecated("moved to XCoroutineView")
class RefreshCoroutinesTreeCommand(
val context: DebuggerContextImpl,
private val debuggerTree: CoroutinesDebuggerTree
) : SuspendContextCommandImpl(context.suspendContext) {
val notification = ProjectNotification(debuggerTree.project)
override fun contextAction() {
val nf = debuggerTree.nodeFactory
val root = nf.defaultNode
val sc: SuspendContextImpl? = suspendContext
if (context.debuggerSession is DebuggerSession && sc is SuspendContextImpl && !sc.isResumed) {
val infoCache = CoroutinesDebugProbesProxy(sc).dumpCoroutines()
val nodeManagerImpl = debuggerTree.nodeFactory
val root = nodeManagerImpl.defaultNode
val suspendContext: SuspendContextImpl? = suspendContext
if (context.debuggerSession is DebuggerSession && suspendContext is SuspendContextImpl && !suspendContext.isResumed) {
var infoCache = CoroutinesDebugProbesProxy(suspendContext).dumpCoroutines()
if (infoCache.isOk()) {
val evaluationContext = sc.createEvaluationContext()
val evaluationContext = evaluationContext(suspendContext)
for (state in infoCache.cache) {
val descriptor = createCoroutineDescriptorNode(nf, state, evaluationContext)
val descriptor = createCoroutineDescriptorNode(nodeManagerImpl, state, evaluationContext)
root.add(descriptor)
}
setRoot(root)
} else {
debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.failure"))
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(
KotlinBundle.message("debugger.session.tab.coroutine.message.error"),
MessageType.ERROR
)
.notify(debuggerTree.project)
notification.error(KotlinBundle.message("debugger.session.tab.coroutine.message.error"))
}
} else {
} else
debuggerTree.showMessage(KotlinBundle.message("debugger.session.tab.coroutine.message.resume"))
}
}
private fun evaluationContext(suspendContext : SuspendContextImpl) =
EvaluationContextImpl(suspendContext, suspendContext.frameProxy)
private fun createCoroutineDescriptorNode(
nodeFactory: NodeManagerImpl,
coroutineInfoData: CoroutineInfoData,
evaluationContext: EvaluationContextImpl
) =
nodeFactory.createNode(
): DebuggerTreeNodeImpl {
return nodeFactory.createNode(
nodeFactory.getDescriptor(
null,
CoroutineDescriptorData(coroutineInfoData)
),
evaluationContext
)
}
private fun setRoot(root: DebuggerTreeNodeImpl) {
DebuggerInvocationUtil.swingInvokeLater(debuggerTree.project) {
@@ -12,6 +12,4 @@ import com.intellij.xdebugger.frame.XNamedValue
class CoroutineAsyncStackFrameItem(
val location: GeneratedLocation,
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(
val name: String,
val state: State,
val threadName: String,
val threadStatus: Int,
val stackTrace: List<StackTraceElement>,
// 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?
) {
var stackFrameList = mutableListOf<CoroutineStackFrameItem>()
@@ -19,6 +19,7 @@ import com.intellij.icons.AllIcons
import com.sun.jdi.ObjectReference
import javax.swing.Icon
@Deprecated("moved to XCoroutineView")
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
lateinit var icon: Icon
@@ -26,10 +27,11 @@ class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorI
@Throws(EvaluateException::class)
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
val thread = infoData.thread
val thread = infoData.activeThread
val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: ""
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
@@ -45,8 +47,9 @@ class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorI
}
}
class CreationFramesDescriptor(val frames: List<StackTraceElement>)
: MessageDescriptor("Coroutine creation stack trace", INFORMATION) {
@Deprecated("moved to XCoroutineView")
class CreationFramesDescriptor(val frames: List<StackTraceElement>) :
MessageDescriptor("Coroutine creation stack trace", INFORMATION) {
override fun isExpandable() = true
}
@@ -54,6 +57,7 @@ class CreationFramesDescriptor(val frames: List<StackTraceElement>)
/**
* Descriptor for suspend functions
*/
@Deprecated("moved to XCoroutineView")
class SuspendStackFrameDescriptor(
val infoData: CoroutineInfoData,
val frame: StackTraceElement,
@@ -64,8 +68,8 @@ class SuspendStackFrameDescriptor(
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
return with(frame) {
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"
}
}
@@ -76,32 +80,35 @@ class SuspendStackFrameDescriptor(
/**
* For the case when no data inside frame is available
*/
@Deprecated("moved to XCoroutineView")
class CoroutineCreatedStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
CoroutineStackFrameDescriptor(proxy) {
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
return with(frame) {
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
}
@Deprecated("moved to XCoroutineView")
class AsyncStackFrameDescriptor(val infoData: CoroutineInfoData, val frame: StackFrameItem, proxy: StackFrameProxyImpl) :
CoroutineStackFrameDescriptor(proxy) {
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
return with(frame) {
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()
}
@Deprecated("moved to XCoroutineView")
open class CoroutineStackFrameDescriptor(proxy: StackFrameProxyImpl) : StackFrameDescriptorImpl(proxy, MethodsTracker()) {
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.evaluate.ExecutionContext
class CoroutineStackTraceData(infoData: CoroutineInfoData, proxy: StackFrameProxyImpl, evalContext: EvaluationContextImpl, val frame: StackTraceElement)
: CoroutineStackDescriptorData(infoData, proxy, evalContext) {
@Deprecated("moved to XCoroutineView")
class CoroutineStackTraceData(
infoData: CoroutineInfoData,
proxy: StackFrameProxyImpl,
evalContext: EvaluationContextImpl,
val frame: StackTraceElement
) : CoroutineStackDescriptorData(infoData, proxy, evalContext) {
override fun hashCode() = frame.hashCode()
@@ -34,13 +39,19 @@ class CoroutineStackTraceData(infoData: CoroutineInfoData, proxy: StackFrameProx
return if (continuation is ObjectReference)
SuspendStackFrameDescriptor(infoData, frame, proxy, continuation)
else
else
CoroutineCreatedStackFrameDescriptor(frame, proxy)
}
}
class CoroutineStackFrameData(infoData: CoroutineInfoData, proxy: StackFrameProxyImpl, evalContext: EvaluationContextImpl, val frame: StackFrameItem)
: CoroutineStackDescriptorData(infoData, proxy, evalContext) {
@Deprecated("moved to XCoroutineView")
class CoroutineStackFrameData(
infoData: CoroutineInfoData,
proxy: StackFrameProxyImpl,
evalContext: EvaluationContextImpl,
val frame: StackFrameItem
) :
CoroutineStackDescriptorData(infoData, proxy, evalContext) {
override fun createDescriptorImpl(project: Project): NodeDescriptorImpl =
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
}
abstract class CoroutineStackDescriptorData(val infoData: CoroutineInfoData, val proxy: StackFrameProxyImpl, val evalContext: EvaluationContextImpl)
: DescriptorData<NodeDescriptorImpl>() {
@Deprecated("moved to XCoroutineView")
abstract class CoroutineStackDescriptorData(
val infoData: CoroutineInfoData,
val proxy: StackFrameProxyImpl,
val evalContext: EvaluationContextImpl
) : DescriptorData<NodeDescriptorImpl>() {
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)
*/
@Deprecated("moved to XCoroutineView")
class CoroutineDescriptorData(private val infoData: CoroutineInfoData) : DescriptorData<CoroutineDescriptorImpl>() {
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.
*/
package org.jetbrains.kotlin.idea.debugger.coroutines
package org.jetbrains.kotlin.idea.debugger.coroutines.proxy
import com.intellij.debugger.DebuggerContext
import com.intellij.debugger.engine.JavaValue
@@ -37,7 +37,7 @@ class AsyncStackTraceContext(
val continuation = locateContinuation() ?: return emptyList()
val frames = mutableListOf<CoroutineAsyncStackFrameItem>()
collectFramesRecursively(continuation, frames)
return frames;
return frames
}
private fun locateContinuation() : ObjectReference? {
@@ -105,7 +105,7 @@ class AsyncStackTraceContext(
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
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)
@@ -4,12 +4,10 @@
*/
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.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.xdebugger.XDebugProcess
import com.intellij.xdebugger.frame.XSuspendContext
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineBuilder
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.evaluate.ExecutionContext
fun SuspendContextImpl.createEvaluationContext() =
EvaluationContextImpl(this, this.frameProxy)
class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
class CoroutinesDebugProbesProxy(val suspendContext: XSuspendContext) {
private val log by logger
// @TODO refactor to extract initialization logic
private var executionContext: ExecutionContext = ExecutionContext(
EvaluationContextImpl(suspendContext, suspendContext.frameProxy),
suspendContext.frameProxy as StackFrameProxyImpl)
private var executionContext: ExecutionContext = executionContext()
// might want to use inner class but also having to monitor order of fields
private var refs: ProcessReferences = ProcessReferences(executionContext)
@@ -89,26 +81,16 @@ class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
val state = getState(instance)
val thread = getLastObservedThread(instance, refs.lastObservedThreadFieldRef)
val lastObservedFrameFieldRef = instance.getValue(refs.lastObservedFrameFieldRef) as? ObjectReference
val stackTrace = getStackTrace(instance)
return CoroutineInfoData(
name,
CoroutineInfoData.State.valueOf(state),
getThreadName(instance),
getThreadState(instance),
getStackTrace(instance),
stackTrace,
thread,
lastObservedFrameFieldRef
)
}
private fun getThreadName(instance: ObjectReference) : String {
return "thread name"
}
private fun getThreadState(instance: ObjectReference) : Int {
return 1
}
private fun getName(
info: ObjectReference // CoroutineInfo instance
): String {
@@ -203,6 +185,11 @@ class CoroutinesDebugProbesProxy(val suspendContext: SuspendContextImpl) {
private fun sizeOf(args: ObjectReference): Int =
(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
* 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.SuspendContextImpl
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
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) {
fun schedule(f: () -> Unit) {
val runnable = object : Runnable {
override fun run() {f()}
class ManagerThreadExecutor(val debugProcess: DebugProcessImpl) {
fun on(suspendContext: SuspendContextImpl, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
ManagerThreadExecutorInstance(suspendContext, priority)
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() {
fun <T> invoke(f: () -> T) : T {
class ApplicationThreadExecutor {
fun <T> readAction(f: () -> T): T {
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
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
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.NodeDescriptorImpl
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.openapi.application.ApplicationManager
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.search.GlobalSearchScope
import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.XSourcePosition
import com.sun.jdi.ClassType
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 com.intellij.xdebugger.impl.XDebuggerManagerImpl
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? {
val psiFacade = JavaPsiFacade.getInstance(project)
val psiClass = ApplicationThreadExecutor().invoke {
val psiClass = ApplicationThreadExecutor().readAction {
@Suppress("DEPRECATION")
psiFacade.findClass(
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)
}
/*
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) :
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
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebugSessionListener
import com.intellij.xdebugger.frame.XSuspendContext
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.sun.jdi.request.EventRequest
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
class CoroutineViewDebugSessionListener(
@@ -19,48 +21,56 @@ class CoroutineViewDebugSessionListener(
val log by logger
override fun sessionPaused() {
log.info("XListener: sessionPaused")
val suspendContext = session.suspendContext ?: return requestClear()
xCoroutineView.forceClear()
xCoroutineView.alarm.cancel()
renew(suspendContext)
}
override fun sessionResumed() {
xCoroutineView.saveState()
log.info("XListener: sessionResumed")
val suspendContext = session.suspendContext ?: return requestClear()
log.warn("sessionResumed ${session}")
renew(suspendContext)
}
override fun sessionStopped() {
log.info("XListener: sessionStopped")
val suspendContext = session.suspendContext ?: return requestClear()
log.warn("sessionStopped ${session}")
renew(suspendContext)
}
override fun stackFrameChanged() {
val suspendContext = session.suspendContext ?: return requestClear()
log.warn("stackFrameChanged ${session}")
renew(suspendContext)
xCoroutineView.saveState()
log.info("XListener: stackFrameChanged")
// val suspendContext = session.suspendContext ?: return requestClear()
// log.warn("stackFrameChanged ${session}")
// renew(suspendContext)
}
override fun beforeSessionResume() {
log.info("XListener: beforeSessionResume")
log.warn("beforeSessionResume ${session}")
}
override fun settingsChanged() {
log.info("XListener: settingsChanged")
val suspendContext = session.suspendContext ?: return requestClear()
log.warn("settingsChanged ${session}")
renew(suspendContext)
}
fun renew(suspendContext: XSuspendContext) {
DebuggerUIUtil.invokeLater {
xCoroutineView.panel.tree.setRoot(xCoroutineView.createRoot(suspendContext), false)
if(suspendContext is SuspendContextImpl && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
DebuggerUIUtil.invokeLater {
xCoroutineView.renewRoot(suspendContext)
}
}
}
fun requestClear() {
private fun requestClear() {
if (ApplicationManager.getApplication().isUnitTestMode) { // no delay in tests
xCoroutineView.clear()
xCoroutineView.resetRoot()
} else {
xCoroutineView.alarm.cancelAndRequest()
}
@@ -28,9 +28,8 @@ import java.util.*
/**
* Tree of coroutines for [CoroutinesPanel]
*/
@Deprecated("moved to XCoroutineView")
class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
private val log by logger
// called on every step/frame
override fun build(context: DebuggerContextImpl) {
val session = context.debuggerSession
@@ -38,9 +37,11 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
val command = RefreshCoroutinesTreeCommand(context, this)
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)
ManagerThreadExecutor(context.debugProcess!!).schedule(command)
context.debugProcess!!.managerThread.schedule(command)
} else {
showMessage(session?.stateDescription ?: DebuggerBundle.message("status.debug.stopped"))
}
@@ -48,7 +49,7 @@ class CoroutinesDebuggerTree(project: Project) : ThreadsDebuggerTree(project) {
override fun getBuildNodeCommand(node: DebuggerTreeNodeImpl): DebuggerCommandImpl? {
return when(val descriptor = node.descriptor) {
return when (val descriptor = node.descriptor) {
is CoroutineDescriptorImpl ->
CoroutineBuildFrameCommand(node, descriptor, myNodeManager, debuggerContext)
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>) {
cache.clear()
@@ -84,11 +86,11 @@ class CoroutineInfoCache(val cache: MutableList<CoroutineInfoData> = mutableList
state = CacheState.FAIL
}
fun isOk() : Boolean {
fun isOk(): Boolean {
return state == CacheState.OK
}
}
enum class CacheState() {
OK,FAIL,INIT
enum class CacheState {
OK, FAIL, INIT
}
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutines.CoroutineDebuggerActions
/**
* Added into ui in [CoroutineProjectConnectionListener.registerCoroutinesPanel]
*/
@Deprecated("moved to XCoroutineView")
class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : ThreadsPanel(project, stateManager) {
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
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaDebugProcess
import com.intellij.debugger.engine.JavaExecutionStack
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.settings.ThreadsViewSettings
import com.intellij.icons.AllIcons
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.openapi.Disposable
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.SimpleColoredComponent
import com.intellij.ui.SimpleColoredText
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.SingleAlarm
import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.XSourcePosition
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.XDebuggerUIConstants
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
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.XValueNodeImpl
import com.sun.jdi.Location
import com.sun.jdi.ReferenceType
import com.sun.jdi.ClassType
import javaslang.control.Either
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.Companion.XCOROUTINE_POPUP_ACTION_GROUP
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutines.command.CreationCoroutineStackFrameItem
import org.jetbrains.kotlin.idea.debugger.coroutines.command.*
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.ManagerThreadExecutor
import org.jetbrains.kotlin.idea.debugger.coroutines.util.CreateContentParams
import org.jetbrains.kotlin.idea.debugger.coroutines.util.CreateContentParamsProvider
import org.jetbrains.kotlin.idea.debugger.coroutines.util.XDebugSessionListenerProvider
import org.jetbrains.kotlin.idea.debugger.coroutines.util.logger
import javax.swing.Icon
import org.jetbrains.kotlin.idea.debugger.coroutines.util.*
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.MouseEvent
class XCoroutineView(val project: Project, val session: XDebugSession) :
Disposable, XDebugSessionListenerProvider, CreateContentParamsProvider {
private var needToRestoreState: Boolean = false
val log by logger
val splitter = OnePixelSplitter("SomeKey", 0.25f)
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
val alarm = SingleAlarm(Runnable { clear() }, VIEW_CLEAR_DELAY, this)
val debugProcess: DebugProcessImpl = (session.debugProcess as JavaDebugProcess).debuggerSession.process
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
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 {
private val VIEW_CLEAR_DELAY = 100 //ms
@@ -60,26 +72,42 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
init {
splitter.firstComponent = panel.mainPanel
selectedNodeListener.installOn()
}
fun clear() {
fun saveState() {
DebuggerUIUtil.invokeLater {
panel.tree
.setRoot(object : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {}) {}, false)
if (! (panel.tree.root is EmptyNode)) {
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() {
restorer?.dispose()
}
fun forceClear() {
alarm.cancel()
clear()
}
fun createRoot(suspendContext: XSuspendContext) =
XCoroutinesRootNode(panel.tree, suspendContext)
override fun debugSessionListener(session: XDebugSession) =
CoroutineViewDebugSessionListener(session, this)
@@ -92,172 +120,211 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
panel.tree
)
}
inner class EmptyNode : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {})
class XCoroutinesRootNode(tree: XDebuggerTree, suspendContext: XSuspendContext) :
XValueContainerNode<CoroutineGroupContainer>(tree, null, false, CoroutineGroupContainer(suspendContext)) {
}
inner class XCoroutinesRootNode(suspendContext: XSuspendContext) :
XValueContainerNode<CoroutineGroupContainer>(panel.tree, null, false, CoroutineGroupContainer(suspendContext, "Default group"))
class CoroutineGroupContainer(
val suspendContext: XSuspendContext
) : XValueContainer() {
override fun computeChildren(node: XCompositeNode) {
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)
inner class CoroutineGroupContainer(val suspendContext: XSuspendContext, val groupName: String) : XValueContainer() {
override fun computeChildren(node: XCompositeNode) {
val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
node.addChildren(groups, true)
}
}
override fun computePresentation(node: XValueNode, place: XValuePlace) {
node.setPresentation(AllIcons.Debugger.ThreadGroup, XRegularValuePresentation(groupName, null, ""), true)
}
}
inner class CoroutineContainer(
val suspendContext: XSuspendContext,
val groupName: String
) : RendererContainer(renderer.renderGroup(groupName)) {
class FramesContainer(
private val coroutineInfoData: CoroutineInfoData,
private val debugProbesProxy: CoroutinesDebugProbesProxy,
private val managerThreadExecutor: ManagerThreadExecutor
) : XValue() {
override fun computeChildren(node: XCompositeNode) {
managerThreadExecutor.schedule {
val children = XValueChildrenList()
debugProbesProxy.frameBuilder().build(coroutineInfoData)
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
coroutineInfoData.stackFrameList.forEach {
val frameValue = when (it) {
is CreationCoroutineStackFrameItem -> {
creationStack.add(it)
null
override fun computeChildren(node: XCompositeNode) {
managerThreadExecutor.on(suspendContext).schedule {
val debugProbesProxy = CoroutinesDebugProbesProxy(suspendContext)
var coroutineCache = debugProbesProxy.dumpCoroutines()
if (coroutineCache.isOk()) {
val children = XValueChildrenList()
coroutineCache.cache.forEach {
children.add(FramesContainer(it, suspendContext))
}
else -> CoroutineFrameValue(it)
}
frameValue?.let {
children.add("", frameValue)
node.addChildren(children, true)
} else {
node.addChildren(XValueChildrenList.singleton(ErrorNode("Error occured while fetching information")), true)
}
}
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)
}
}
override fun computePresentation(node: XValueNode, place: XValuePlace) {
val icon = when (coroutineInfoData.state) {
CoroutineInfoData.State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended
CoroutineInfoData.State.RUNNING -> AllIcons.Debugger.ThreadRunning
CoroutineInfoData.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
inner class CoroutineFrameValue(val frame: CoroutineStackFrameItem
) : XNamedValue(frame.uniqueId()) {
override fun computePresentation(node: XValueNode, place: XValuePlace) =
applyRenderer(node, renderer.render(frame.location()))
}
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)
node.setPresentation(icon, valuePresentation, true)
}
fun nodeSelected(event: Either<MouseEvent, KeyEvent>) : Boolean {
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()
val thread = coroutineInfoData.thread
val name = thread?.name()?.substringBefore(" @${coroutineInfoData.name}") ?: ""
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
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)
}
// else -> {
// val (stack, stackFrame) = createSyntheticStackFrame(descriptor, pos) ?: return
// val action: () -> Unit = { context.debuggerSession?.xDebugSession?.setCurrentStackFrame(stack, stackFrame) }
// - ApplicationManager.getApplication()
// - .invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
// }
}
}
}
return false
}
}
if (settings.SHOW_SOURCE_NAME) {
label.append(", ", SimpleTextAttributes.REGULAR_ATTRIBUTES)
label.append(DebuggerUtilsEx.getSourceName(location) { e: Throwable? -> "Unknown Source" }, SimpleTextAttributes.REGULAR_ATTRIBUTES)
fun createSyntheticStackFrame() {}
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
}