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