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