From 38ec388429b7f8d9360d4d5f4ac3081e8b2fb55d Mon Sep 17 00:00:00 2001 From: Vladimir Ilmov Date: Mon, 23 Mar 2020 19:12:55 +0100 Subject: [PATCH] (CoroutineDebugger) Group coroutines by dispatcher Kotlin Script support added. #KT-37917 fixed --- .../CoroutineAsyncStackTraceProvider.kt | 30 +-- .../CoroutineStackFrameInterceptor.kt | 4 +- .../debugger/coroutine/DebuggerConnection.kt | 3 +- .../debugger/coroutine/DebuggerListener.kt | 4 +- .../coroutine/command/CoroutineBuilder.kt | 53 ++--- .../data/CoroutineAsyncStackFrameItem.kt | 31 ++- .../coroutine/data/CoroutineInfoData.kt | 95 ++++++-- .../coroutine/data/TreeNodeDescriptorImpl.kt | 2 +- .../coroutine/proxy/ContinuationHolder.kt | 209 +++++++----------- .../proxy/CoroutineDebugProbesProxy.kt | 2 +- .../coroutine/proxy/CoroutineHolder.kt | 64 ------ .../proxy/CoroutineLibraryAgent2Proxy.kt | 79 +++++++ .../proxy/CoroutineLibraryAgentProxy.kt | 6 +- .../proxy/CoroutineNoLibraryProxy.kt | 10 +- .../proxy/CoroutinePreflightStackFrame.kt | 58 ++++- .../coroutine/proxy/CoroutineUtils.kt | 13 +- .../debugger/coroutine/proxy/FieldVariable.kt | 44 ---- .../debugger/coroutine/proxy/LocationCache.kt | 46 ++++ .../proxy/LocationStackFrameProxyImpl.kt | 8 +- .../proxy/mirror/coroutinesContextMirror.kt | 34 ++- .../proxy/mirror/coroutinesDebugMirror.kt | 192 ++++++++++++++++ .../proxy/mirror/coroutinesMirror.kt | 119 +++++++--- .../coroutine/proxy/mirror/debugMetadata.kt | 60 +++++ .../coroutine/proxy/mirror/javaLangMirror.kt | 22 +- .../coroutine/view/CoroutineDumpPanel.kt | 19 +- .../debugger/coroutine/view/XCoroutineView.kt | 60 ++--- .../view/XDebuggerTreeSelectedNodeListener.kt | 13 +- .../test/AbstractAsyncStackTraceTest.kt | 9 +- .../debugger/evaluate/ExecutionContext.kt | 3 + 29 files changed, 862 insertions(+), 430 deletions(-) delete mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineHolder.kt create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt delete mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/FieldVariable.kt create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationCache.kt create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt index a30dee49f92..fb2eb5dae1c 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt @@ -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? { - val resumeWithFrame = stackFrame.resumeWithFrame + val resumeWithFrame = stackFrame.threadPreCoroutineFrames.firstOrNull() if (threadAndContextSupportsEvaluation(suspendContext, resumeWithFrame)) { val stackFrames = mutableListOf() - 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? { + suspendContext: SuspendContextImpl, + framesLeft: List + ): 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 } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt index 2b797b612aa..618b8d68bbf 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt @@ -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 } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerConnection.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerConnection.kt index 56a9bf6095f..ac2928287f5 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerConnection.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerConnection.kt @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerListener.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerListener.kt index 615117194d5..4921929bf04 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerListener.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/DebuggerListener.kt @@ -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 } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt index 89285b4e84d..df5a683d4a9 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt @@ -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 } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt index 0cb484fa114..ec2df33774d 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt @@ -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) } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt index 20fe773460b..8de00cf00e9 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt @@ -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, + val stackTrace: MutableList, val creationStackTrace: List, val activeThread: ThreadReference? = null, // for suspended coroutines should be null - val lastObservedFrameFieldRef: ObjectReference? + val lastObservedFrameFieldRef: ObjectReference? = null ) { - var stackFrameList = mutableListOf() - - // @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 ): CoroutineInfoData? { - return CoroutineInfoData(holder.info, holder.stackFrameItems, emptyList(), null, lastObservedFrameFieldRef) + val locationCache = LocationCache(context) + val creationStackTrace = mutableListOf() + 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, diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/TreeNodeDescriptorImpl.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/TreeNodeDescriptorImpl.kt index c349d8f44e9..d8158e59248 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/TreeNodeDescriptorImpl.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/TreeNodeDescriptorImpl.kt @@ -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 { diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/ContinuationHolder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/ContinuationHolder.kt index 05bddb55bce..321c43cfc6c 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/ContinuationHolder.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/ContinuationHolder.kt @@ -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() 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? { - debugMetadataKtType()?.let { - return getSpilledVariables(this, it) - } - return null - } - - private fun getSpilledVariables(continuation: ContinuationHolder, debugMetadataKtType: ClassType): List? { val variables: List = 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? { + 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 + ): 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 -// } } + } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt index d3c380a5b23..c7191a88b66 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt @@ -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) } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineHolder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineHolder.kt deleted file mode 100644 index 3720d7e873c..00000000000 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineHolder.kt +++ /dev/null @@ -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 -) { - companion object { - fun lookup( - value: ObjectReference?, - context: DefaultExecutionContext, - stackFrameItems: List - ): 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 - } - } -} - - diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt new file mode 100644 index 00000000000..104110d9d28 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt @@ -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 { + 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): 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, + val creationStackFrames: List + ) + + companion object { + fun instance(executionContext: DefaultExecutionContext): CoroutineLibraryAgent2Proxy? { + val agentProxy = CoroutineLibraryAgent2Proxy(executionContext) + if (agentProxy.isInstalled()) + return agentProxy + else + return null + } + } + +} diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgentProxy.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgentProxy.kt index 9530345afb2..1f56adfe046 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgentProxy.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgentProxy.kt @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineNoLibraryProxy.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineNoLibraryProxy.kt index c4ec68e333b..78c3b428d0f 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineNoLibraryProxy.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineNoLibraryProxy.kt @@ -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): 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() diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutinePreflightStackFrame.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutinePreflightStackFrame.kt index 237c922f907..912ff21e58e 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutinePreflightStackFrame.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutinePreflightStackFrame.kt @@ -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, + val coroutineInfoData: CoroutineInfoData, val stackFrameDescriptorImpl: StackFrameDescriptorImpl, - val threadPreCoroutineFrames: List + val threadPreCoroutineFrames: List, ) : 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, + coroutineInfoData: CoroutineInfoData, originalFrames: List ): 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 } + } \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineUtils.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineUtils.kt index 4ffc9214845..f6a5221b6cf 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineUtils.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineUtils.kt @@ -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? = vm.classesByName("kotlinx.coroutines.DispatchedContinuation") fun DefaultExecutionContext.findCancellableContinuationImplReferenceType(): List? = vm.classesByName("kotlinx.coroutines.CancellableContinuationImpl") -fun findGetCoroutineSuspended(frames: List) = +fun hasGetCoroutineSuspended(frames: List) = 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) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/FieldVariable.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/FieldVariable.kt deleted file mode 100644 index ea58fca8454..00000000000 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/FieldVariable.kt +++ /dev/null @@ -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 { - 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() - 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) - } - } -} - diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationCache.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationCache.kt new file mode 100644 index 00000000000..143bdf24d2e --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationCache.kt @@ -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) + } + + +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationStackFrameProxyImpl.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationStackFrameProxyImpl.kt index ca109e656f6..479b4de3270 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationStackFrameProxyImpl.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationStackFrameProxyImpl.kt @@ -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 } -} \ No newline at end of file +} + + +class SkipCoroutineStackFrameProxyImpl(frame: StackFrameProxyImpl) : + StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt index 8fac8c2c87e..2294fe748d5 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt @@ -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("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 getElementValue(value: ObjectReference, context: DefaultExecutionContext, keyProvider: ContextKey): 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(name: String, context: DefaultExecutionContext) : BaseMirror(name, context) { - abstract fun key() : ObjectReference + abstract fun key() : ObjectReference? } class CoroutineName(context: DefaultExecutionContext) : ContextKey("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("kotl class CoroutineId(context: DefaultExecutionContext) : ContextKey("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("kotli } override fun key() = key -} \ No newline at end of file +} + + +class CoroutineDispatcher(context: DefaultExecutionContext) : ContextKey("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 +} diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt new file mode 100644 index 00000000000..b0de7c724c1 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt @@ -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("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? { + 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 { + 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("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?, + val creationStackTrace: List?, + val state: String?, + val lastObservedThread: ThreadReference?, + val lastObservedFrame: ObjectReference? +) + +class CoroutineStackFrame(context: DefaultExecutionContext) : + BaseMirror("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("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 + ) +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt index e260aaa1b6c..1812d603159 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt @@ -13,50 +13,90 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext abstract class BaseMirror(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("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("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("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("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) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt new file mode 100644 index 00000000000..19daf3dd172 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt @@ -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("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 { + val getSpilledVariableFieldMappingReference = staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference ?: return emptyList() + + val length = getSpilledVariableFieldMappingReference.length() / 2 + val fieldVariables = ArrayList() + 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) \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/javaLangMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/javaLangMirror.kt index 3a720d20572..b05aaab015a 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/javaLangMirror.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/javaLangMirror.kt @@ -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("java.util.List", context) { + val sizeMethod = makeMethod("size") + val getMethod = makeMethod("get") + + override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfJavaLangList? { + val list = mutableListOf() + 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) + diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt index bd802b5fad4..09a754bc42a 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutineDumpPanel.kt @@ -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" } -} \ No newline at end of file +} + +private fun stringStackTrace(info: CoroutineInfoData) = + buildString { + appendln("\"${info.key.name}\", state: ${info.key.state}") + info.stackTrace.forEach { + appendln("\t$it") + } + } + diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt index 345ec8cabf7..0df08a08060 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt @@ -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() 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( 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? ) : 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()) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XDebuggerTreeSelectedNodeListener.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XDebuggerTreeSelectedNodeListener.kt index cd9b199bb05..be95bc1dc7c 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XDebuggerTreeSelectedNodeListener.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XDebuggerTreeSelectedNodeListener.kt @@ -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) }) } diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt index 49852b6eff1..f6fbbfe16d1 100644 --- a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractAsyncStackTraceTest.kt @@ -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) } diff --git a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt index fc6fb8c68b4..f55504da52f 100644 --- a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt +++ b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt @@ -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? { + 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