From 3cd0eb0fca3b851de6903a3f8648fcf2cbf37f3a Mon Sep 17 00:00:00 2001 From: Vladimir Ilmov Date: Tue, 14 Apr 2020 14:08:59 +0200 Subject: [PATCH] (CoroutineDebugger) Stack printing logic extracted to the single place for DebugMetadata and CoroutinePanel, some corner cases fixed, test cases added. Added: - external maven dependencies in test scenarios - comparing stack traces + variables on breakpoint --- .../kotlin/generators/tests/GenerateTests.kt | 8 + .../KotlinDebuggerCoroutinesBundle.properties | 1 - .../CoroutineAsyncStackTraceProvider.kt | 54 +--- .../CoroutineDebugConfigurationExtension.java | 7 +- .../CoroutineStackFrameInterceptor.kt | 28 +- .../debugger/coroutine/DebuggerConnection.kt | 2 - .../coroutine/command/CoroutineBuilder.kt | 61 ----- .../data/ContinuationValueDescriptorImpl.kt | 5 +- .../data/CoroutineAsyncStackFrameItem.kt | 115 +++----- .../coroutine/data/CoroutineInfoData.kt | 75 +----- .../coroutine/data/TreeNodeDescriptorImpl.kt | 2 +- .../coroutine/data/coroutineStackFrames.kt | 116 ++++++++ .../coroutine/proxy/ContinuationHolder.kt | 253 +++++++----------- .../proxy/CoroutineDebugProbesProxy.kt | 6 +- .../proxy/CoroutineLibraryAgent2Proxy.kt | 37 +-- .../proxy/CoroutineLibraryAgentProxy.kt | 42 +-- .../proxy/CoroutineNoLibraryProxy.kt | 10 +- .../proxy/CoroutinePreflightStackFrame.kt | 101 ------- .../debugger/coroutine/proxy/LocationCache.kt | 4 +- .../proxy/LocationStackFrameProxyImpl.kt | 1 - .../proxy/mirror/coroutinesDebugMirror.kt | 47 +++- .../proxy/mirror/coroutinesMirror.kt | 22 +- .../coroutine/proxy/mirror/debugMetadata.kt | 90 +++++-- .../coroutine/proxy/mirror/javaLangMirror.kt | 28 +- .../coroutine/util/CoroutineFrameBuilder.kt | 177 ++++++++++++ .../{proxy => util}/CoroutineUtils.kt | 47 ++-- .../idea/debugger/coroutine/util/KUtils.kt | 36 ++- .../idea/debugger/coroutine/util/Providers.kt | 7 +- .../coroutine/view/SimpleColoredTextIcon.kt | 2 +- .../debugger/coroutine/view/XCoroutineView.kt | 21 +- .../view/XDebuggerTreeSelectedNodeListener.kt | 58 ++-- .../test/AbstractAsyncStackTraceTest.kt | 35 +-- .../AbstractContinuationStackTraceTest.kt | 58 ++++ .../test/AbstractXCoroutinesStackTraceTest.kt | 47 ++++ .../ContinuationStackTraceTestGenerated.java | 40 +++ .../test/DebuggerTestCompilerFacility.kt | 18 +- .../debugger/test/KotlinDescriptorTestCase.kt | 15 +- ...KotlinDescriptorTestCaseWithStackFrames.kt | 146 ++++++++++ .../XCoroutinesStackTraceTestGenerated.java | 35 +++ .../debugger/test/util/XDebuggerTestUtil.java | 115 ++++++++ .../test/util/XTestCompositeNode.java | 29 ++ .../debugger/test/util/XTestContainer.java | 59 ++++ .../testData/continuation/suspendFun.kt | 30 +++ .../testData/continuation/suspendLambda.kt | 26 ++ .../testData/continuation/suspendLambda.out | 28 ++ .../xcoroutines/coroutineSuspendFun.kt | 30 +++ .../xcoroutines/coroutineSuspendFun.out | 47 ++++ 47 files changed, 1487 insertions(+), 734 deletions(-) delete mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrames.kt delete mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutinePreflightStackFrame.kt create mode 100644 idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt rename idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/{proxy => util}/CoroutineUtils.kt (78%) create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractContinuationStackTraceTest.kt create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractXCoroutinesStackTraceTest.kt create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/ContinuationStackTraceTestGenerated.java create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/XCoroutinesStackTraceTestGenerated.java create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XDebuggerTestUtil.java create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestCompositeNode.java create mode 100644 idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestContainer.java create mode 100644 idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt create mode 100644 idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt create mode 100644 idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out create mode 100644 idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt create mode 100644 idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 69fd467e6e6..9d725529e1e 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -240,6 +240,14 @@ fun main(args: Array) { // TODO: implement mapping logic for terminal operations model("sequence/streams/sequence", excludeDirs = listOf("terminal")) } + + testClass { + model("continuation") + } + + testClass { + model("xcoroutines") + } } testGroup("idea/tests", "idea/testData") { diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/resources/messages/KotlinDebuggerCoroutinesBundle.properties b/idea/jvm-debugger/jvm-debugger-coroutine/resources/messages/KotlinDebuggerCoroutinesBundle.properties index ebeff96a218..ad2036146e3 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/resources/messages/KotlinDebuggerCoroutinesBundle.properties +++ b/idea/jvm-debugger/jvm-debugger-coroutine/resources/messages/KotlinDebuggerCoroutinesBundle.properties @@ -15,7 +15,6 @@ coroutine.dump.full.title=Full coroutine dump coroutine.dump.full.copied=Full coroutine dump was successfully copied to clipboard coroutine.dump.creation.trace=Coroutine creation stack trace -coroutine.dump.creation.frame=Creation stack frame of {0} coroutine.dump.threads.loading=Loading… 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 fb2eb5dae1c..6ccdceecb2c 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 @@ -8,21 +8,19 @@ package org.jetbrains.kotlin.idea.debugger.coroutine import com.intellij.debugger.engine.AsyncStackTraceProvider 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.CoroutinePreflightStackFrame import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem +import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder +import org.jetbrains.kotlin.idea.debugger.coroutine.util.threadAndContextSupportsEvaluation import org.jetbrains.kotlin.idea.debugger.hopelessAware -import org.jetbrains.kotlin.idea.debugger.isInKotlinSources class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider { override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List? { val stackFrameList = hopelessAware { if (stackFrame is CoroutinePreflightStackFrame) - lookupForAfterPreflight(stackFrame, suspendContext) + processPreflight(stackFrame, suspendContext) else null } @@ -31,45 +29,21 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider { else stackFrameList } - private fun lookupForAfterPreflight( - stackFrame: CoroutinePreflightStackFrame, + private fun processPreflight( + preflightFrame: CoroutinePreflightStackFrame, suspendContext: SuspendContextImpl ): List? { - val resumeWithFrame = stackFrame.threadPreCoroutineFrames.firstOrNull() + val resumeWithFrame = preflightFrame.threadPreCoroutineFrames.firstOrNull() - if (threadAndContextSupportsEvaluation(suspendContext, resumeWithFrame)) { - val stackFrames = mutableListOf() - stackFrames.addAll(stackFrame.coroutineInfoData.stackTrace) - -// 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 - PreCoroutineStackFrameItem(stackFrameProxyImpl) - }) - stackFrames.addAll(stackFrame.coroutineInfoData.creationStackTrace) - return stackFrames + if (threadAndContextSupportsEvaluation( + suspendContext, + resumeWithFrame + ) + ) { + val doubleFrameList = CoroutineFrameBuilder.build(preflightFrame, suspendContext) + return doubleFrameList.stackTrace + doubleFrameList.creationStackTrace } return null } - - fun lookupForResumeContinuation( - frameProxy: StackFrameProxyImpl, - suspendContext: SuspendContextImpl, - framesLeft: List - ): CoroutinePreflightStackFrame? { - val location = frameProxy.location() - if (!location.isInKotlinSources()) - return null - - if (threadAndContextSupportsEvaluation(suspendContext, frameProxy)) - return ContinuationHolder.lookupContinuation(suspendContext, frameProxy, framesLeft) - return null - } - - 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/CoroutineDebugConfigurationExtension.java b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineDebugConfigurationExtension.java index 1b300c8a361..e7a50b7e63f 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineDebugConfigurationExtension.java +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineDebugConfigurationExtension.java @@ -12,14 +12,15 @@ import com.intellij.execution.configurations.RunnerSettings; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; public class CoroutineDebugConfigurationExtension extends RunConfigurationExtension { private static final Logger log = Logger.getInstance(CoroutineDebugConfigurationExtension.class); @Override public void updateJavaParameters( - T configuration, JavaParameters params, RunnerSettings runnerSettings - ) throws ExecutionException { + @NotNull T configuration, @NotNull JavaParameters params, RunnerSettings runnerSettings + ) { if (configuration != null) { Project project = configuration.getProject(); DebuggerListener listener = ServiceManager.getService(project, DebuggerListener.class); @@ -31,7 +32,7 @@ public class CoroutineDebugConfigurationExtension extends RunConfigurationExtens } @Override - public boolean isApplicableFor(RunConfigurationBase configuration) { + public boolean isApplicableFor(@NotNull RunConfigurationBase configuration) { return true; } } 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 618b8d68bbf..65c70eb44e9 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 @@ -9,6 +9,8 @@ import com.intellij.debugger.actions.AsyncStacksToggleAction import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.jdi.StackFrameProxyImpl +import com.intellij.openapi.application.Application +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.xdebugger.frame.XStackFrame import com.intellij.xdebugger.impl.XDebugSessionImpl @@ -16,14 +18,26 @@ 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 +import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder +import org.jetbrains.kotlin.idea.debugger.coroutine.util.isInUnitTest 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) - else + override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? { + val stackFrame = if (debugProcess.xdebugProcess?.session is XDebugSessionImpl + && frame !is SkipCoroutineStackFrameProxyImpl + && AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl) + ) { + val suspendContextImpl = when { + isInUnitTest() -> debugProcess.suspendManager.pausedContext + else -> debugProcess.debuggerContext.suspendContext + } + if (!isInUnitTest()) + assert(debugProcess.suspendManager.pausedContext === debugProcess.debuggerContext.suspendContext) + suspendContextImpl?.let { + CoroutineFrameBuilder.coroutineExitFrame(frame, it) + } + } else null + return stackFrame + } } \ 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 2a90cab4d78..5eab83595fd 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 @@ -100,5 +100,3 @@ class DebuggerConnection( return ui.createContent(param.id, param.component, param.displayName, param.icon, param.parentComponent) } } - -fun coroutineDebuggerTraceEnabled() = Registry.`is`("kotlin.debugger.coroutines.trace") 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 deleted file mode 100644 index df5a683d4a9..00000000000 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.idea.debugger.coroutine.command - -import com.intellij.debugger.engine.SuspendContextImpl -import com.intellij.debugger.jdi.StackFrameProxyImpl -import com.intellij.debugger.jdi.ThreadReferenceProxyImpl -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 -import org.jetbrains.kotlin.idea.debugger.safeMethod - - -class CoroutineBuilder(val suspendContext: SuspendContextImpl) { - private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider() - val debugProcess = suspendContext.debugProcess - - companion object { - const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as creation stacktrace separator in kotlinx.coroutines - } - - fun build(coroutine: CoroutineInfoData): List { - val coroutineStackFrameList = mutableListOf() - - if (coroutine.isRunning() && coroutine.activeThread is ThreadReference) { - val threadReferenceProxyImpl = ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, coroutine.activeThread) - - val realFrames = threadReferenceProxyImpl.forceFrames() - for (runningStackFrameProxy in realFrames) { - if (runningStackFrameProxy.location().isPreFlight()) { - 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 - ) - } - coroutineStackFrameList.addAll(stackFrameItems) - } else - coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy)) - } - } else if (coroutine.isSuspended()) - coroutineStackFrameList.addAll(coroutine.stackTrace) - - coroutineStackFrameList.addAll(coroutine.creationStackTrace) - return coroutineStackFrameList - } -} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/ContinuationValueDescriptorImpl.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/ContinuationValueDescriptorImpl.kt index 4dc35ed5f5c..4fe5d861009 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/ContinuationValueDescriptorImpl.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/ContinuationValueDescriptorImpl.kt @@ -10,12 +10,13 @@ import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl import com.intellij.openapi.project.Project +import com.sun.jdi.ObjectReference import com.sun.jdi.Value import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder class ContinuationValueDescriptorImpl( project: Project, - val continuation: ContinuationHolder, + val continuation: ObjectReference, val fieldName: String, val variableName: String ) : ValueDescriptorImpl(project) { @@ -23,7 +24,7 @@ class ContinuationValueDescriptorImpl( override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? { val field = continuation.referenceType()?.fieldByName(fieldName) ?: return null - return continuation.field(field) + return continuation.getValue(field) } override fun getDescriptorEvaluation(context: DebuggerContext?) = 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 ec2df33774d..1df111e6950 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,77 +7,57 @@ 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 -import com.intellij.ui.ColoredTextContainer -import com.intellij.ui.SimpleTextAttributes -import com.intellij.xdebugger.frame.XCompositeNode 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.isInvokeSuspend import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger +/** + * Creation frame of coroutine either in RUNNING or SUSPENDED state. + */ +class CreationCoroutineStackFrameItem( + val stackTraceElement: StackTraceElement, + location: Location, + val first: Boolean +) : CoroutineStackFrameItem(location, emptyList()) { + fun descriptor(frame: StackFrameProxyImpl) = + RestoredStackFrameDescriptor(stackTraceElement, frame) -class FirstCreationCoroutineStackFrameItem( - stackTraceElement: StackTraceElement, - location: Location -) : CreationCoroutineStackFrameItem(stackTraceElement, location) { override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame { - return CreationCoroutineStackFrame(debugProcess, this) + return if (first) + CreationCoroutineStackFrame(debugProcess, this) + else + super.createFrame(debugProcess) } } -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()) { - fun emptyDescriptor(frame: StackFrameProxyImpl) = - EmptyStackFrameDescriptor(stackTraceElement, frame) -} - /** - * Suspended frames in Suspended coroutine + * Restored frame in SUSPENDED coroutine, not attached to any thread. */ class SuspendCoroutineStackFrameItem( val stackTraceElement: StackTraceElement, location: Location, spilledVariables: List = emptyList() ) : CoroutineStackFrameItem(location, spilledVariables) { - fun emptyDescriptor(frame: StackFrameProxyImpl) = - EmptyStackFrameDescriptor(stackTraceElement, frame) + + fun descriptor(frame: StackFrameProxyImpl) = + RestoredStackFrameDescriptor(stackTraceElement, frame) } -class RunningCoroutineStackFrameItem( - val frame: StackFrameProxyImpl, -// val stackFrame: XStackFrame, - spilledVariables: List = emptyList() -) : CoroutineStackFrameItem(frame.location(), spilledVariables) - /** - * Restored frame in Running coroutine, attaching to running thread + * Restored frame in RUNNING coroutine, attached to running thread. Frame references a 'preflight' or 'exit' frame. */ class RestoredCoroutineStackFrameItem( val frame: StackFrameProxyImpl, location: Location, spilledVariables: List ) : CoroutineStackFrameItem(location, spilledVariables) { - fun emptyDescriptor() = + + fun descriptor() = StackFrameDescriptorImpl(frame, MethodsTracker()) } @@ -87,7 +67,7 @@ class RestoredCoroutineStackFrameItem( class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List) : CoroutineStackFrameItem(location, spilledVariables) { - fun emptyDescriptor(frame: StackFrameProxyImpl) = + fun descriptor(frame: StackFrameProxyImpl) = StackFrameDescriptorImpl(frame, MethodsTracker()) } @@ -100,10 +80,10 @@ class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List< * - invokeSuspend(KotlinStackFrame) -| * | replaced with CoroutinePreflightStackFrame * - resumeWith(KotlinStackFrame) ----| - * - PreCoroutineStackFrameItem part of CoroutinePreflightStackFrame + * - Kotlin/JavaStackFrame -> PreCoroutineStackFrameItem : CoroutinePreflightStackFrame.threadPreCoroutineFrames * */ -class PreCoroutineStackFrameItem(val frame: StackFrameProxyImpl, location: Location, variables: List = emptyList()) : +class PreCoroutineStackFrameItem internal constructor(val frame: StackFrameProxyImpl, location: Location, variables: List = emptyList()) : CoroutineStackFrameItem(location, variables) { constructor(frame: StackFrameProxyImpl, variables: List = emptyList()) : this(frame, frame.location(), variables) @@ -118,49 +98,34 @@ class PreCoroutineStackFrameItem(val frame: StackFrameProxyImpl, location: Locat } } -/** - * 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 { - val skipCoroutineFrame = SkipCoroutineStackFrameProxyImpl(frame) - debugProcess.getPositionManager().createStackFrame(skipCoroutineFrame, debugProcess, frame.location()) - ?.computeChildren(node) // hack but works +class RunningCoroutineStackFrameItem( + val frame: StackFrameProxyImpl, + location: Location, + spilledVariables: List = emptyList() +) : CoroutineStackFrameItem(location, spilledVariables) { + override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame { + val realStackFrame = debugProcess.invokeInManagerThread { + debugProcess.positionManager.createStackFrame(frame, debugProcess, location) } - super.computeChildren(node) + return CoroutineStackFrame(debugProcess, this, realStackFrame) } } -open class CoroutineStackFrame(debugProcess: DebugProcessImpl, val item: StackFrameItem) : - StackFrameItem.CapturedStackFrame(debugProcess, item) { - override fun customizePresentation(component: ColoredTextContainer) { - if (coroutineDebuggerTraceEnabled()) - component.append("${item.javaClass.simpleName} / ${this.javaClass.simpleName} ", SimpleTextAttributes.GRAYED_ATTRIBUTES) - super.customizePresentation(component) - } - - override fun getCaptionAboveOf() = "CoroutineExit" - - override fun hasSeparatorAbove(): Boolean = - false -} - - sealed class CoroutineStackFrameItem(val location: Location, val spilledVariables: List) : StackFrameItem(location, spilledVariables) { val log by logger + override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame = + CoroutineStackFrame(debugProcess, this) + fun uniqueId(): String { return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" + location.safeLineNumber() + ":" + location.safeKotlinPreferredLineNumber() } - override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame { - return CoroutineStackFrame(debugProcess, this) - } + fun isInvokeSuspend(): Boolean = + location.isInvokeSuspend() } -class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) : +class RestoredStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) : StackFrameDescriptorImpl(proxy, MethodsTracker()) 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 8de00cf00e9..cd8baabae1c 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,11 +7,8 @@ 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.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. @@ -19,10 +16,10 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext */ data class CoroutineInfoData( val key: CoroutineNameIdState, - val stackTrace: MutableList, + val stackTrace: List, val creationStackTrace: List, val activeThread: ThreadReference? = null, // for suspended coroutines should be null - val lastObservedFrameFieldRef: ObjectReference? = null + val lastObservedFrame: ObjectReference? = null ) { fun isSuspended() = key.state == State.SUSPENDED @@ -32,66 +29,22 @@ data class CoroutineInfoData( fun isRunning() = key.state == State.RUNNING + fun topRestoredFrame() = stackTrace.firstOrNull() + + fun topFrameVariables() = stackTrace.firstOrNull()?.spilledVariables ?: emptyList() + + fun restoredStackTrace(mode: SuspendExitMode): List = + if (stackTrace.isNotEmpty() && stackTrace.first().isInvokeSuspend()) + stackTrace.drop(1) + else if (mode == SuspendExitMode.SUSPEND_METHOD_PARAMETER) + stackTrace.drop(1) + else + stackTrace + companion object { 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? { - 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 - } } } 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 d8158e59248..c349d8f44e9 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/data/coroutineStackFrames.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrames.kt new file mode 100644 index 00000000000..3094c8a538e --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineStackFrames.kt @@ -0,0 +1,116 @@ +/* + * 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.data + +import com.intellij.debugger.engine.DebugProcessImpl +import com.intellij.debugger.engine.JVMStackFrameInfoProvider +import com.intellij.debugger.jdi.LocalVariableProxyImpl +import com.intellij.debugger.jdi.StackFrameProxyImpl +import com.intellij.debugger.memory.utils.StackFrameItem +import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl +import com.intellij.ui.ColoredTextContainer +import com.intellij.ui.SimpleTextAttributes +import com.intellij.xdebugger.frame.XCompositeNode +import com.intellij.xdebugger.frame.XNamedValue +import com.intellij.xdebugger.frame.XStackFrame +import com.intellij.xdebugger.frame.XValueChildrenList +import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle +import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl +import org.jetbrains.kotlin.idea.debugger.coroutine.util.coroutineDebuggerTraceEnabled +import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread +import org.jetbrains.kotlin.idea.debugger.stackFrame.KotlinStackFrame + + +/** + * Coroutine exit frame represented by a stack frames + * invokeSuspend():-1 + * resumeWith() + * + */ + +class CoroutinePreflightStackFrame( + val coroutineInfoData: CoroutineInfoData, + val stackFrameDescriptorImpl: StackFrameDescriptorImpl, + val threadPreCoroutineFrames: List, + val mode: SuspendExitMode, + val firstFrameVariables: List = coroutineInfoData.topFrameVariables() +) : KotlinStackFrame(stackFrameDescriptorImpl), JVMStackFrameInfoProvider { + + override fun computeChildren(node: XCompositeNode) { + val childrenList = XValueChildrenList() + firstFrameVariables.forEach { + childrenList.add(it) + } + node.addChildren(childrenList, false) + super.computeChildren(node) + } + + override fun getVisibleVariables(): List { + // skip restored variables + return super.getVisibleVariables().filter { v -> firstFrameVariables.find { it.name == v.name() } == null } + } + + override fun isInLibraryContent() = + false + + override fun isSynthetic() = + false + + fun restoredStackTrace() = + coroutineInfoData.restoredStackTrace(mode) +} + +enum class SuspendExitMode { + SUSPEND_LAMBDA, SUSPEND_METHOD_PARAMETER, SUSPEND_METHOD, UNKNOWN, NONE; + + fun isCoroutineFound() = + this == SUSPEND_LAMBDA || this == SUSPEND_METHOD_PARAMETER + + fun isSuspendMethodParameter() = + this == SuspendExitMode.SUSPEND_METHOD_PARAMETER +} + +class CreationCoroutineStackFrame(debugProcess: DebugProcessImpl, item: StackFrameItem) : CoroutineStackFrame(debugProcess, item) { + override fun getCaptionAboveOf() = KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.trace") + + override fun hasSeparatorAbove(): Boolean = + true +} + +/** + * Acts as a joint frame, take variables from restored frame and information from the real 'exit' frame. + */ +class PreCoroutineStackFrame(val frame: StackFrameProxyImpl, val debugProcess: DebugProcessImpl, item: StackFrameItem) : + CoroutineStackFrame(debugProcess, item) { + override fun computeChildren(node: XCompositeNode) { + val fakeStackFrame = debugProcess.invokeInManagerThread { + val skipCoroutineFrame = SkipCoroutineStackFrameProxyImpl(frame) + debugProcess.positionManager.createStackFrame(skipCoroutineFrame, debugProcess, frame.location()) + } + fakeStackFrame?.computeChildren(node) +// super.computeChildren(node) + } +} + +open class CoroutineStackFrame(debugProcess: DebugProcessImpl, val item: StackFrameItem, val realStackFrame: XStackFrame? = null) : + StackFrameItem.CapturedStackFrame(debugProcess, item) { + override fun customizePresentation(component: ColoredTextContainer) { + if (coroutineDebuggerTraceEnabled()) + component.append("${item.javaClass.simpleName} / ${this.javaClass.simpleName} ", SimpleTextAttributes.GRAYED_ATTRIBUTES) + super.customizePresentation(component) + } + override fun computeChildren(node: XCompositeNode) { + if (realStackFrame != null) + realStackFrame.computeChildren(node) + else + super.computeChildren(node) + } + + + override fun getCaptionAboveOf() = "CoroutineExit" + + override fun hasSeparatorAbove(): Boolean = + false +} 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 321c43cfc6c..888d7894b0e 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 @@ -6,195 +6,128 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy import com.intellij.debugger.engine.JavaValue -import com.intellij.debugger.engine.SuspendContextImpl -import com.intellij.debugger.jdi.GeneratedLocation -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.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.coroutine.proxy.mirror.* +import org.jetbrains.kotlin.idea.debugger.coroutine.util.* 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 +class ContinuationHolder(val context: DefaultExecutionContext) { private val debugMetadata: DebugMetadata? = DebugMetadata.instance(context) + private val locationCache = LocationCache(context) + private val debugProbesImpl = DebugProbesImpl.instance(context) + private val log by logger - fun getCoroutineInfoData(): CoroutineInfoData? { + fun extractCoroutineInfoData(continuation: ObjectReference): CoroutineInfoData? { try { - return collectCoroutineInfo() + val consumer = mutableListOf() + val continuationStack = debugMetadata?.fetchContinuationStack(continuation, context) ?: return null + for (frame in continuationStack.coroutineStack) { + val coroutineStackFrame = createStackFrameItem(frame) + if (coroutineStackFrame != null) + consumer.add(coroutineStackFrame) + } + val lastRestoredFrame = continuationStack.coroutineStack.last() + return findCoroutineInformation(lastRestoredFrame.baseContinuationImpl.coroutineOwner, consumer) } catch (e: Exception) { log.error("Error while looking for stack frame.", e) } return null } - private fun collectCoroutineInfo(): CoroutineInfoData? { - val consumer = mutableListOf() - var completion = this - while (completion.isBaseContinuationImpl()) { - val coroutineStackFrame = context.debugProcess.invokeInManagerThread { - completion.createLocation() + private fun findCoroutineInformation( + input: ObjectReference?, + stackFrameItems: List + ): CoroutineInfoData? { + val creationStackTrace = mutableListOf() + val realState = if (input?.type()?.isAbstractCoroutine() == true) { + state(input) ?: return null + } else { + val ci = debugProbesImpl?.getCoroutineInfo(input, context) + if (ci != null) { + if (ci.creationStackTrace != null) + for (index in 0 until ci.creationStackTrace.size) { + val frame = ci.creationStackTrace.get(index) + val ste = frame.stackTraceElement() + val location = locationCache.createLocation(ste) + creationStackTrace.add(CreationCoroutineStackFrameItem(ste, location, index == 0)) + } + CoroutineNameIdState.instance(ci) + } else { + CoroutineInfoData.log.warn("Coroutine agent information not found.") + CoroutineNameIdState(CoroutineInfoData.DEFAULT_COROUTINE_NAME, "-1", State.UNKNOWN, null) } - if (coroutineStackFrame != null) { - consumer.add(coroutineStackFrame) - } - completion = completion.findCompletion() ?: break } - return CoroutineInfoData.lookup(completion.value(), context, consumer) + return CoroutineInfoData(realState, stackFrameItems, creationStackTrace) } - 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) - } - - fun getSpilledVariables(): List? { - val variables: List = context.debugProcess.invokeInManagerThread { - debugMetadata?.getSpilledVariableFieldMapping(continuation, context)?.mapNotNull { - fieldVariableToNamedValue(it, this) + fun state(value: ObjectReference?): 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 ?: CoroutineInfoData.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) } - } ?: emptyList() - return variables - } - - 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? = - continuation.referenceType() as? ClassType - - fun value() = - continuation - - fun field(field: Field): Value? = - continuation.getValue(field) - - fun findCompletion(): ContinuationHolder? { - val type = continuation.type() - if (type is ClassType && type.isBaseContinuationImpl()) { - val completionField = type.completionField() ?: return null - return ContinuationHolder(continuation.getValue(completionField) as? ObjectReference ?: return null, context) } return null } - fun isBaseContinuationImpl() = - continuation.type().isBaseContinuationImpl() + private fun createStackFrameItem( + frame: MirrorOfStackFrame + ): DefaultCoroutineStackFrameItem? { + val stackTraceElement = frame.baseContinuationImpl.stackTraceElement?.stackTraceElement() ?: return null + val locationClass = context.findClassSafe(stackTraceElement.className) ?: return null + val generatedLocation = locationCache.createLocation(locationClass, stackTraceElement.methodName, stackTraceElement.lineNumber) + val spilledVariables = frame.baseContinuationImpl.spilledValues(context) + return DefaultCoroutineStackFrameItem(generatedLocation, spilledVariables) + } companion object { val log by logger - fun coroutineExitFrame( - frame: StackFrameProxyImpl, - suspendContext: SuspendContextImpl? - ): XStackFrame? { - suspendContext ?: return null - return suspendContext.invokeInManagerThread { - if (frame.location().isPreFlight() || frame.location().isPreExitFrame()) { - if (coroutineDebuggerTraceEnabled()) - log.trace("Entry frame found: ${formatLocation(frame.location())}") - val leftThreadStack = leftThreadStack(frame) ?: return@invokeInManagerThread null - lookupContinuation(suspendContext, frame, leftThreadStack) - } else - 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 } - } - - 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()}" - } - - /** - * Find continuation for the [frame] - * Gets current CoroutineInfo.lastObservedFrame and finds next frames in it until null or needed stackTraceElement is found - * @return null if matching continuation is not found or is not BaseContinuationImpl - */ - fun lookup( - context: SuspendContextImpl, - initialContinuation: ObjectReference?, - ): ContinuationHolder? { - var continuation = initialContinuation ?: return null - val executionContext = context.executionContext() ?: return null - - do { - continuation = getNextFrame(executionContext, continuation) ?: return null - } while (continuation.type().isBaseContinuationImpl() /* && position != classLine */) - - return if (continuation.type().isBaseContinuationImpl()) - ContinuationHolder(continuation, executionContext) - else - return null - } } - } +fun MirrorOfBaseContinuationImpl.spilledValues(context: DefaultExecutionContext): List { + return fieldVariables.mapNotNull { + it.toJavaValue(that, context) + } +} + +fun FieldVariable.toJavaValue(continuation: ObjectReference, context: DefaultExecutionContext): JavaValue { + val valueDescriptor = ContinuationValueDescriptorImpl( + context.project, + continuation, + fieldName, + variableName + ) + return JavaValue.create( + null, + valueDescriptor, + context.evaluationContext, + context.debugProcess.xdebugProcess!!.nodeManager, + false + ) + +} 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 502ac8d8d51..cb1d20b8e17 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 @@ -7,9 +7,9 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy import com.intellij.debugger.engine.DebuggerManagerThreadImpl import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.openapi.util.registry.Registry -import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineBuilder +import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoCache -import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData +import org.jetbrains.kotlin.idea.debugger.coroutine.util.executionContext import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext @@ -44,8 +44,6 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) { return CoroutineNoLibraryProxy(executionContext) return null } - - fun frameBuilder() = CoroutineBuilder(suspendContext) } fun standaloneCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines.standalone") 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 index 104110d9d28..f986271392b 100644 --- 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 @@ -5,17 +5,12 @@ 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 com.intellij.xdebugger.frame.XNamedValue 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.DebugMetadata 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.isCreationSeparatorFrame import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext @@ -24,6 +19,7 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution val log by logger private val debugProbesImpl = DebugProbesImpl(executionContext) private val locationCache = LocationCache(executionContext) + private val debugMetadata: DebugMetadata? = DebugMetadata.instance(executionContext) override fun dumpCoroutinesInfo(): List { val result = debugProbesImpl.dumpCoroutinesInfo(executionContext) @@ -33,10 +29,14 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution fun mapToCoroutineInfoData(mirror: MirrorOfCoroutineInfo): CoroutineInfoData? { val cnis = CoroutineNameIdState.instance(mirror) val stackTrace = mirror.enchancedStackTrace?.mapNotNull { it.stackTraceElement() } ?: emptyList() - var stackFrames = findStackFrames(stackTrace) + val variables: List = mirror.lastObservedFrame?.let { + val spilledVariables = debugMetadata?.baseContinuationImpl?.mirror(it, executionContext) + spilledVariables?.spilledValues(executionContext) + } ?: emptyList() + var stackFrames = findStackFrames(stackTrace, variables) return CoroutineInfoData( cnis, - stackFrames.restoredStackFrames.toMutableList(), + stackFrames.restoredStackFrames, stackFrames.creationStackFrames, mirror.lastObservedThread, mirror.lastObservedFrame @@ -52,13 +52,18 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution } } - private fun findStackFrames(frames: List): CoroutineStackFrames { + private fun findStackFrames( + frames: List, + variables: 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)) - }) + val restoredStackFrames = frames.take(index).map { + SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it), variables) + } + val creationStackFrames = frames.subList(index + 1, frames.size).mapIndexed { ix, it -> + CreationCoroutineStackFrameItem(it, locationCache.createLocation(it), ix == 0) + } + return CoroutineStackFrames(restoredStackFrames, creationStackFrames) } data class CoroutineStackFrames( 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 1f56adfe046..eccd96ec88b 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 @@ -5,17 +5,12 @@ 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.CoroutineInfo -import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror +import org.jetbrains.kotlin.idea.debugger.coroutine.util.isCreationSeparatorFrame import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, private val executionContext: DefaultExecutionContext) : @@ -43,8 +38,7 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva // value private val vm = executionContext.vm - private val classesByName = ClassesByNameProvider.createCache(vm.allClasses()) - + private val locationCache = LocationCache(executionContext) private val coroutineContext: CoroutineContext = CoroutineContext(executionContext) @@ -79,11 +73,11 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva val coroutineStackTrace = stackTrace.take(creationFrameSeparatorIndex) val coroutineStackTraceFrameItems = coroutineStackTrace.map { - SuspendCoroutineStackFrameItem(it, createLocation(it)) + SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it)) } val creationStackTrace = stackTrace.subList(creationFrameSeparatorIndex + 1, stackTrace.size) - val creationStackTraceFrameItems = creationStackTrace.map { - CreationCoroutineStackFrameItem(it, createLocation(it)) + val creationStackTraceFrameItems = creationStackTrace.mapIndexed { index, stackTraceElement -> + CreationCoroutineStackFrameItem(stackTraceElement, locationCache.createLocation(stackTraceElement), index == 0) } val key = CoroutineNameIdState(name, "", State.valueOf(state), "") @@ -96,32 +90,6 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva ) } - - private 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(executionContext.debugProcess, type, methodName, line) - } - /** * Tries to find creation frame separator if any, returns last index if none found */ 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 78c3b428d0f..63275a9d69e 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 @@ -9,12 +9,16 @@ import com.intellij.openapi.util.registry.Registry import com.sun.jdi.* import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CancellableContinuationImpl +import org.jetbrains.kotlin.idea.debugger.coroutine.util.findCancellableContinuationImplReferenceType +import org.jetbrains.kotlin.idea.debugger.coroutine.util.findCoroutineMetadataType +import org.jetbrains.kotlin.idea.debugger.coroutine.util.findDispatchedContinuationReferenceType import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : CoroutineInfoProvider { val log by logger val debugMetadataKtType = executionContext.findCoroutineMetadataType() + val holder = ContinuationHolder(executionContext) override fun dumpCoroutinesInfo(): List { val vm = executionContext.vm @@ -51,7 +55,7 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C ): CoroutineInfoData? { val mirror = ccMirrorProvider.mirror(dispatchedContinuation, executionContext) ?: return null val continuation = mirror.delegate?.continuation ?: return null - return ContinuationHolder(continuation, executionContext).getCoroutineInfoData() + return holder.extractCoroutineInfoData(continuation) } private fun dispatchedContinuation(resultList: MutableList): Boolean { @@ -68,10 +72,10 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C return false } - fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? { + private fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? { debugMetadataKtType ?: return null val initialContinuation = dispatchedContinuation.getValue(continuation) as ObjectReference - return ContinuationHolder(initialContinuation, executionContext).getCoroutineInfoData() + return holder.extractCoroutineInfoData(initialContinuation) } } 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 deleted file mode 100644 index 912ff21e58e..00000000000 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutinePreflightStackFrame.kt +++ /dev/null @@ -1,101 +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.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 - -/** - * Coroutine exit frame represented by a stack frames - * invokeSuspend():-1 - * resumeWith() - * - */ - -class CoroutinePreflightStackFrame( - val coroutineInfoData: CoroutineInfoData, - val stackFrameDescriptorImpl: StackFrameDescriptorImpl, - val threadPreCoroutineFrames: List, -) : KotlinStackFrame(stackFrameDescriptorImpl), JVMStackFrameInfoProvider { - - override fun computeChildren(node: XCompositeNode) { - val childrenList = XValueChildrenList() - val firstRestoredCoroutineStackFrameItem = coroutineInfoData.stackTrace.firstOrNull() ?: return - firstRestoredCoroutineStackFrameItem.spilledVariables.forEach { - childrenList.add(it) - } - node.addChildren(childrenList, false) - super.computeChildren(node) - } - - override fun isInLibraryContent() = - false - - override fun isSynthetic() = - false - - companion object { - fun preflight( - invokeSuspendFrame: StackFrameProxyImpl, - coroutineInfoData: CoroutineInfoData, - originalFrames: List - ): CoroutinePreflightStackFrame? { - val descriptor = createFirstRestoredFrame(invokeSuspendFrame, coroutineInfoData) - return CoroutinePreflightStackFrame( - coroutineInfoData, - descriptor, - originalFrames.filter { ! isInvokeSuspendNegativeLineMethodFrame(it) } - ) - } - - 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/LocationCache.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/LocationCache.kt index 143bdf24d2e..9ec1df0bfef 100644 --- 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 @@ -17,13 +17,13 @@ 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( + fun createLocation(stackTraceElement: StackTraceElement): Location = createLocation( ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]), stackTraceElement.methodName, stackTraceElement.lineNumber ) - private fun findLocation( + fun createLocation( type: ReferenceType?, methodName: String, line: Int 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 479b4de3270..f322598cf49 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 @@ -17,6 +17,5 @@ class LocationStackFrameProxyImpl(val location: Location, frame: StackFrameProxy } } - 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/coroutinesDebugMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt index b0de7c724c1..d1c50d06932 100644 --- 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 @@ -6,13 +6,15 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror import com.sun.jdi.* +import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext -class DebugProbesImpl(context: DefaultExecutionContext) : +class DebugProbesImpl internal constructor(context: DefaultExecutionContext) : BaseMirror("kotlinx.coroutines.debug.internal.DebugProbesImpl", context) { - val javaLangListMirror = JavaUtilList(context) + val javaLangListMirror = JavaUtilAbstractCollection(context) val stackTraceElement = StackTraceElement(context) val coroutineInfo = CoroutineInfo(this, context) + val debugProbesCoroutineOwner = DebugProbesImpl_CoroutineOwner(coroutineInfo, context) val instance = staticObjectValue("INSTANCE") val isInstalledMethod = makeMethod("isInstalled\$kotlinx_coroutines_debug", "()Z") val isInstalledValue = booleanValue(instance, isInstalledMethod, context) @@ -41,20 +43,46 @@ class DebugProbesImpl(context: DefaultExecutionContext) : 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) + fun getCoroutineInfo(value: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? { + val coroutineOwner = debugProbesCoroutineOwner.mirror(value, context) + return coroutineOwner?.coroutineInfo + } + + companion object { + fun instance(context: DefaultExecutionContext) = + try { + DebugProbesImpl(context) + } catch (e: IllegalStateException) { + null + } } } +class DebugProbesImpl_CoroutineOwner(val coroutineInfo: CoroutineInfo, context: DefaultExecutionContext) : + BaseMirror(COROUTINE_OWNER_CLASS_NAME, context) { + private val infoField = makeField("info") + + override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineOwner? { + val info = objectValue(value, infoField) + return MirrorOfCoroutineOwner(value, coroutineInfo.mirror(info, context)) + } + + companion object { + const val COROUTINE_OWNER_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugProbesImpl\$CoroutineOwner" + + fun instanceOf(value: ObjectReference?) = + value?.let { it.referenceType().isSubTypeOrSame(COROUTINE_OWNER_CLASS_NAME) } ?: false + } +} + +data class MirrorOfCoroutineOwner(val that: ObjectReference, val coroutineInfo: MirrorOfCoroutineInfo?) + 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) + val javaLangListMirror = JavaUtilAbstractCollection(context) private val coroutineContextMirror = CoroutineContext(context) private val coroutineStackFrameMirror = CoroutineStackFrame(context) private val stackTraceElement = StackTraceElement(context) @@ -182,6 +210,9 @@ data class MirrorOfStackTraceElement( val lineNumber: Int?, val format: Byte? ) { + fun isInvokeSuspend() = + "invokeSuspend" == methodName + fun stackTraceElement() = java.lang.StackTraceElement( declaringClass, 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 1812d603159..1583a336d3d 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 @@ -6,7 +6,7 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror import com.sun.jdi.* -import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.isSubTypeOrSame +import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext @@ -24,8 +24,8 @@ abstract class BaseMirror(val name: String, context: DefaultExecutionContext) fun makeMethod(methodName: String, signature: String): Method? = cls?.let { it.methodsByName(methodName, signature).single() } - fun isCompatible(value: ObjectReference) = - value.referenceType().isSubTypeOrSame(name) + fun isCompatible(value: ObjectReference?) = + value?.let { it.referenceType().isSubTypeOrSame(name) } ?: false fun mirror(value: ObjectReference?, context: DefaultExecutionContext): T? { value ?: return null @@ -56,16 +56,24 @@ abstract class BaseMirror(val name: String, context: DefaultExecutionContext) } fun stringValue(value: ObjectReference, field: Field?) = - (value.getValue(field) as? StringReference)?.value() + field?.let { + (value.getValue(it) as? StringReference)?.value() + } fun byteValue(value: ObjectReference, field: Field?) = - (value.getValue(field) as? ByteValue)?.value() + field?.let { + (value.getValue(it) as? ByteValue)?.value() + } fun threadValue(value: ObjectReference, field: Field?) = - value.getValue(field) as? ThreadReference + field?.let { + value.getValue(it) as? ThreadReference + } fun stringValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext) = - method?.let { (context.invokeMethod(value, it, emptyList()) as? StringReference)?.value() } + method?.let { + (context.invokeMethod(value, it, emptyList()) as? StringReference)?.value() + } fun objectValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value) = value?.let { 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 index 19daf3dd172..59a641f3b38 100644 --- 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 @@ -9,29 +9,76 @@ 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.coroutine.util.isBaseContinuationImpl import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext -class DebugMetadata(context: DefaultExecutionContext) : +class DebugMetadata internal constructor(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) + private val getStackTraceElementMethod = makeMethod("getStackTraceElement") + private val getSpilledVariableFieldMappingMethod = + makeMethod("getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;") + val baseContinuationImpl = BaseContinuationImpl(context, this); override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? { + // @TODO fix this 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 fetchContinuationStack(continuation: ObjectReference, context: DefaultExecutionContext): MirrorOfContinuationStack { + val coroutineStack = mutableListOf() + var loopContinuation: ObjectReference? = continuation + while (loopContinuation != null) { + val continuationMirror = baseContinuationImpl.mirror(loopContinuation, context) ?: break + coroutineStack.add(MirrorOfStackFrame(loopContinuation, continuationMirror)) + loopContinuation = continuationMirror.nextContinuation + } + return MirrorOfContinuationStack(continuation, coroutineStack) } - fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext): List { - val getSpilledVariableFieldMappingReference = staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference ?: return emptyList() + fun getStackTraceElement(value: ObjectReference, context: DefaultExecutionContext) = + staticMethodValue(getStackTraceElementMethod, context, value) + + fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext) = + staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference + + companion object { + fun instance(context: DefaultExecutionContext) = + try { + DebugMetadata(context) + } catch (e: IllegalStateException) { + null + } + } +} + +data class MirrorOfContinuationStack(val that: ObjectReference, val coroutineStack: List) + +data class MirrorOfStackFrame( + val that: ObjectReference, + val baseContinuationImpl: MirrorOfBaseContinuationImpl +) + +data class FieldVariable(val fieldName: String, val variableName: String) + +class BaseContinuationImpl(context: DefaultExecutionContext, private val debugMetadata: DebugMetadata) : + BaseMirror("kotlin.coroutines.jvm.internal.BaseContinuationImpl", context) { + + private val getCompletion = makeMethod("getCompletion", "()Lkotlin/coroutines/Continuation;") + private val stackTraceElement = StackTraceElement(context) + + override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfBaseContinuationImpl? { + val stackTraceObjectReference = debugMetadata.getStackTraceElement(value, context) ?: return null + val stackTraceElementMirror = stackTraceElement.mirror(stackTraceObjectReference, context) + val fieldVariables = getSpilledVariableFieldMapping(value, context) + val completionValue = objectValue(value, getCompletion, context) + val completion = if (completionValue != null && isCompatible(completionValue)) completionValue else null + val coroutineOwner = if (completionValue != null && DebugProbesImpl_CoroutineOwner.instanceOf(completionValue)) completionValue else null + return MirrorOfBaseContinuationImpl(value, stackTraceElementMirror, fieldVariables, completion, coroutineOwner) + } + + private fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext): List { + val getSpilledVariableFieldMappingReference = + debugMetadata.getSpilledVariableFieldMapping(value, context) ?: return emptyList() val length = getSpilledVariableFieldMappingReference.length() / 2 val fieldVariables = ArrayList() @@ -46,15 +93,12 @@ class DebugMetadata(context: DefaultExecutionContext) : 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 +data class MirrorOfBaseContinuationImpl( + val that: ObjectReference, + val stackTraceElement: MirrorOfStackTraceElement?, + val fieldVariables: List, + val nextContinuation: ObjectReference?, + val coroutineOwner: ObjectReference? +) \ 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 b05aaab015a..a528ec0c62b 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 @@ -61,21 +61,33 @@ class JavaLangMirror(context: DefaultExecutionContext) { (instance.getValue(declaringClassFieldRef) as? StringReference)?.value() ?: "" } -class JavaUtilList(context: DefaultExecutionContext) : - BaseMirror("java.util.List", context) { +class JavaUtilAbstractCollection(context: DefaultExecutionContext) : + BaseMirror("java.util.AbstractCollection", context) { + val abstractList = JavaUtilAbstractList(context) val sizeMethod = makeMethod("size") - val getMethod = makeMethod("get") - override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfJavaLangList? { + override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfJavaLangAbstractCollection? { 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 + for (index in 0 until size) { + val reference = abstractList.get(value, index, context) ?: continue list.add(reference) } - return MirrorOfJavaLangList(value, list) + return MirrorOfJavaLangAbstractCollection(value, list) } } -data class MirrorOfJavaLangList(val that: ObjectReference, val values: List) +class JavaUtilAbstractList(context: DefaultExecutionContext) : + BaseMirror("java.util.AbstractList", context) { + val getMethod = makeMethod("get") + + override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext) = + null + + fun get(value: ObjectReference, index: Int, context: DefaultExecutionContext): ObjectReference? = + objectValue(value, getMethod, context, context.vm.mirrorOf(index)) +} + + +data class MirrorOfJavaLangAbstractCollection(val that: ObjectReference, val values: List) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt new file mode 100644 index 00000000000..18e883cb29d --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt @@ -0,0 +1,177 @@ +/* + * 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.util + +import com.intellij.debugger.engine.SuspendContextImpl +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.sun.jdi.ObjectReference +import org.jetbrains.kotlin.idea.debugger.coroutine.data.* +import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder +import org.jetbrains.kotlin.idea.debugger.safeLineNumber +import org.jetbrains.kotlin.idea.debugger.safeLocation +import org.jetbrains.kotlin.idea.debugger.safeMethod + + +class CoroutineFrameBuilder { + + companion object { + val log by logger + + fun build(coroutine: CoroutineInfoData, suspendContext: SuspendContextImpl): DoubleFrameList? = + when { + coroutine.isRunning() -> buildStackFrameForActive(coroutine, suspendContext) + coroutine.isSuspended() -> DoubleFrameList(coroutine.stackTrace, coroutine.creationStackTrace) + else -> null + } + + private fun buildStackFrameForActive(coroutine: CoroutineInfoData, suspendContext: SuspendContextImpl): DoubleFrameList? { + val activeThread = coroutine.activeThread ?: return null + + val coroutineStackFrameList = mutableListOf() + val threadReferenceProxyImpl = ThreadReferenceProxyImpl(suspendContext.debugProcess.virtualMachineProxy, activeThread) + val realFrames = threadReferenceProxyImpl.forceFrames() + for (runningStackFrameProxy in realFrames) { + val preflightStackFrame = coroutineExitFrame(runningStackFrameProxy, suspendContext) + if (preflightStackFrame != null) { + coroutineStackFrameList.add(buildRealStackFrameItem(preflightStackFrame.stackFrameProxy,)) + val doubleFrameList = build(preflightStackFrame, suspendContext) + coroutineStackFrameList.addAll(doubleFrameList.stackTrace) + return DoubleFrameList(coroutineStackFrameList, doubleFrameList.creationStackTrace) + } else { + coroutineStackFrameList.add(buildRealStackFrameItem(runningStackFrameProxy,)) + } + } + return DoubleFrameList(coroutineStackFrameList, emptyList()) + } + + /** + * Used by CoroutineAsyncStackTraceProvider to build XFramesView + */ + fun build(preflightFrame: CoroutinePreflightStackFrame, suspendContext: SuspendContextImpl): DoubleFrameList { + val stackFrames = mutableListOf() + + stackFrames.addAll(preflightFrame.restoredStackTrace()) + + // rest of the stack + stackFrames.addAll(preflightFrame.threadPreCoroutineFrames.drop(1).mapIndexedNotNull { index, stackFrameProxyImpl -> +// if (index == 0) +// PreCoroutineStackFrameItem(stackFrameProxyImpl, firstRestoredFrame) // get location and variables from restored part +// else + suspendContext.invokeInManagerThread { buildRealStackFrameItem(stackFrameProxyImpl) } + }) + + return DoubleFrameList(stackFrames, preflightFrame.coroutineInfoData.creationStackTrace) + } + + data class DoubleFrameList( + val stackTrace: List, + val creationStackTrace: List + ) + + private fun buildRealStackFrameItem( + frame: StackFrameProxyImpl + ): RunningCoroutineStackFrameItem { + val location = frame.location() + return RunningCoroutineStackFrameItem(frame, location) + } + + + /** + * Used by CoroutineStackFrameInterceptor to check if that frame is 'exit' coroutine frame. + */ + fun coroutineExitFrame( + frame: StackFrameProxyImpl, + suspendContext: SuspendContextImpl + ): CoroutinePreflightStackFrame? { + return suspendContext.invokeInManagerThread { + val sem = frame.location().isPreFlight() + if (sem.isCoroutineFound()) { + if (coroutineDebuggerTraceEnabled()) + ContinuationHolder.log.debug("Entry frame found: ${frame.format()}") + lookupContinuation(suspendContext, frame, sem) + } else + null + } + } + + private fun filterNegativeLineNumberInvokeSuspendFrames(frame: StackFrameProxyImpl): Boolean { + val method = frame.safeLocation()?.safeMethod() ?: return false + return method.isInvokeSuspend() && frame.safeLocation()?.safeLineNumber() ?: 0 < 0 + } + + fun lookupContinuation( + suspendContext: SuspendContextImpl, + frame: StackFrameProxyImpl, + mode: SuspendExitMode + ): CoroutinePreflightStackFrame? { + if (!mode.isCoroutineFound()) + return null + + val theFollowingFrames = theFollowingFrames(frame) ?: emptyList() + if (mode.isSuspendMethodParameter()) { + if (theFollowingFrames.isNotEmpty()) { + // have to check next frame if that's invokeSuspend:-1 before proceed, otherwise skip + val theFollowingFrame = theFollowingFrames.first() + val theFollowingMode = theFollowingFrame.location().isPreFlight() + if (theFollowingMode != SuspendExitMode.SUSPEND_METHOD) + return null // break, that's not the exit, perhaps the following one? + } else + return null + } + + if (threadAndContextSupportsEvaluation(suspendContext, frame)) { + val context = suspendContext.executionContext() ?: return null + val continuation = when (mode) { + SuspendExitMode.SUSPEND_LAMBDA -> getThisContinuation(frame) + SuspendExitMode.SUSPEND_METHOD_PARAMETER -> getLVTContinuation(frame) + else -> null + } ?: return null + + val coroutineInfo = ContinuationHolder(context).extractCoroutineInfoData(continuation) ?: return null + return preflight(frame, theFollowingFrames, coroutineInfo, mode) + } + return null + } + + private fun preflight( + frame: StackFrameProxyImpl, + framesLeft: List, + coroutineInfoData: CoroutineInfoData, + mode: SuspendExitMode + ): CoroutinePreflightStackFrame? { + val descriptor = StackFrameDescriptorImpl(frame, MethodsTracker()) + return CoroutinePreflightStackFrame( + coroutineInfoData, + descriptor, + framesLeft, + mode + ) + } + + private fun getLVTContinuation(frame: StackFrameProxyImpl?) = + frame?.continuationVariableValue() + + private fun getThisContinuation(frame: StackFrameProxyImpl?): ObjectReference? = + frame?.thisVariableValue() + + fun theFollowingFrames(frame: StackFrameProxyImpl): List? { + val 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 && frames.size > indexOfCurrentFrame + 1) + return frames.drop(indexOfCurrentFrame + 1) + } else { + log.error("Frame isn't found on the thread stack.") + } + return null + } + } +} \ 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/util/CoroutineUtils.kt similarity index 78% rename from idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineUtils.kt rename to idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt index f6a5221b6cf..e328713b90d 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/util/CoroutineUtils.kt @@ -3,7 +3,7 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package org.jetbrains.kotlin.idea.debugger.coroutine.proxy +package org.jetbrains.kotlin.idea.debugger.coroutine.util import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.engine.evaluation.EvaluationContextImpl @@ -17,10 +17,12 @@ import com.intellij.xdebugger.XDebuggerUtil import com.intellij.xdebugger.XSourcePosition import com.sun.jdi.* import org.jetbrains.kotlin.idea.debugger.* -import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineBuilder +import org.jetbrains.kotlin.idea.debugger.coroutine.data.SuspendExitMode import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext import org.jetbrains.kotlin.idea.util.application.isUnitTestMode +const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as creation stacktrace separator in kotlinx.coroutines + fun Method.isInvokeSuspend(): Boolean = name() == "invokeSuspend" && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" @@ -30,12 +32,21 @@ fun Method.isContinuation() = fun Method.isSuspendLambda() = isInvokeSuspend() && declaringType().isSuspendLambda() +fun Method.hasContinuationParameter() = + signature().contains("Lkotlin/coroutines/Continuation;)") + fun Method.isResumeWith() = name() == "resumeWith" && signature() == "(Ljava/lang/Object;)V" && (declaringType().isSuspendLambda() || declaringType().isContinuation()) -fun Location.isPreFlight(): Boolean { - val method = safeMethod() ?: return false - return method.isSuspendLambda() || method.isContinuation() +fun Location.isPreFlight(): SuspendExitMode { + val method = safeMethod() ?: return SuspendExitMode.NONE + if (method.isSuspendLambda()) + return SuspendExitMode.SUSPEND_LAMBDA + else if (method.hasContinuationParameter()) + return SuspendExitMode.SUSPEND_METHOD_PARAMETER + else if (method.isInvokeSuspend() && safeLineNumber() == -1) + return SuspendExitMode.SUSPEND_METHOD + return SuspendExitMode.NONE } fun ReferenceType.isContinuation() = @@ -53,8 +64,8 @@ fun Type.isSubTypeOrSame(className: String) = fun ReferenceType.isSuspendLambda() = SUSPEND_LAMBDA_CLASSES.any { isSubtype(it) } -fun Location.isPreExitFrame() = - safeMethod()?.isResumeWith() ?: false +fun Location.isInvokeSuspend() = + safeMethod()?.isInvokeSuspend() ?: false fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? { val continuationVariable = safeVisibleVariableByName(variableName) ?: return null @@ -64,8 +75,8 @@ fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? { fun StackFrameProxyImpl.completionVariableValue(): ObjectReference? = variableValue("completion") -fun StackFrameProxyImpl.completion1VariableValue(): ObjectReference? = - variableValue("completion") +fun StackFrameProxyImpl.continuationVariableValue(): ObjectReference? = + variableValue("\$continuation") fun StackFrameProxyImpl.thisVariableValue(): ObjectReference? = this.thisObject() @@ -86,7 +97,7 @@ fun hasGetCoroutineSuspended(frames: List) = frames.indexOfFirst { it.safeLocation()?.safeMethod()?.isGetCOROUTINE_SUSPENDED() == true } fun StackTraceElement.isCreationSeparatorFrame() = - className.startsWith(CoroutineBuilder.CREATION_STACK_TRACE_SEPARATOR) + className.startsWith(CREATION_STACK_TRACE_SEPARATOR) fun StackTraceElement.findPosition(project: Project): XSourcePosition? = getPosition(project, className, lineNumber) @@ -108,17 +119,6 @@ private fun getPosition(project: Project, className: String, lineNumber: Int): X val localLineNumber = if (lineNumber > 0) lineNumber - 1 else return null return XDebuggerUtil.getInstance().createPosition(classFile, localLineNumber) } -/** - * Finds previous Continuation for this Continuation (completion field in BaseContinuationImpl) - * @return null if given ObjectReference is not a BaseContinuationImpl instance or completion is null - */ -fun getNextFrame(context: DefaultExecutionContext, continuation: ObjectReference): ObjectReference? { - if (!continuation.type().isBaseContinuationImpl()) - return null - val type = continuation.type() as ClassType - val next = type.concreteMethodByName("getCompletion", "()Lkotlin/coroutines/Continuation;") - return context.invokeMethod(continuation, next, emptyList()) as? ObjectReference -} fun SuspendContextImpl.executionContext() = invokeInManagerThread { DefaultExecutionContext(EvaluationContextImpl(this, this.frameProxy)) } @@ -134,3 +134,8 @@ fun SuspendContextImpl.supportsEvaluation() = fun XDebugSession.suspendContextImpl() = suspendContext as SuspendContextImpl + +fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl?) = + suspendContext.invokeInManagerThread { + suspendContext.supportsEvaluation() && frameProxy?.threadProxy()?.supportsEvaluation() ?: false + } ?: false diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/KUtils.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/KUtils.kt index 94db9914ff8..497131db0d3 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/KUtils.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/KUtils.kt @@ -5,14 +5,24 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.util +import com.intellij.debugger.engine.JavaStackFrame +import com.intellij.debugger.jdi.StackFrameProxyImpl +import com.intellij.debugger.memory.utils.StackFrameItem +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.openapi.ui.MessageType +import com.intellij.openapi.util.registry.Registry import com.intellij.psi.JavaPsiFacade import com.intellij.psi.search.GlobalSearchScope import com.intellij.xdebugger.XDebuggerUtil import com.intellij.xdebugger.XSourcePosition +import com.intellij.xdebugger.frame.XStackFrame import com.intellij.xdebugger.impl.XDebuggerManagerImpl +import com.sun.jdi.Location import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor +import org.jetbrains.kotlin.idea.debugger.safeLineNumber +import org.jetbrains.kotlin.idea.debugger.safeLocation +import org.jetbrains.kotlin.idea.debugger.safeMethod fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSourcePosition? { val psiFacade = JavaPsiFacade.getInstance(project) @@ -31,7 +41,27 @@ fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSource return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber) } -class ProjectNotification(val project: Project) { - fun error(message: String) = - XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project) +fun Location.format(): String { + val method = safeMethod() + return "${method?.name() ?: "noname"}:${safeLineNumber()}, ${method?.declaringType()?.name() ?: "empty"}" } + +fun JavaStackFrame.format(): String { + val location = descriptor.location + return location?.let { it.format() } ?: "emptyLocation" +} + +fun StackFrameItem.format(): String { + val method = this.method() + val type = this.path() + val lineNumber = this.line() + return "$method:$lineNumber, $type" +} + +fun StackFrameProxyImpl.format(): String { + return safeLocation()?.format() ?: "emptyLocation" +} + +fun isInUnitTest() = ApplicationManager.getApplication().isUnitTestMode + +fun coroutineDebuggerTraceEnabled() = Registry.`is`("kotlin.debugger.coroutines.trace") || isInUnitTest() diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Providers.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Providers.kt index f4aa05c6b71..8f5d5bed4f7 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Providers.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/Providers.kt @@ -5,9 +5,13 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.util +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.DefaultLogger import com.intellij.openapi.diagnostic.Logger import com.intellij.xdebugger.XDebugSession import com.intellij.xdebugger.XDebugSessionListener +import org.apache.log4j.Level +import org.jetbrains.kotlin.idea.util.application.isUnitTestMode import javax.swing.Icon import javax.swing.JComponent import kotlin.properties.ReadOnlyProperty @@ -33,8 +37,9 @@ class LoggerDelegate : ReadOnlyProperty { lateinit var logger: Logger override fun getValue(thisRef: Any, property: KProperty<*>): Logger { - if (!::logger.isInitialized) + if (!::logger.isInitialized) { logger = Logger.getInstance(thisRef.javaClass) + } return logger } } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt index 7469e98f4ef..13d7653a49b 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/SimpleColoredTextIcon.kt @@ -181,7 +181,7 @@ class SimpleColoredTextIconPresentationRenderer { SimpleColoredTextIcon( AllIcons.Debugger.ThreadSuspended, true, - KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.frame", infoData.key.name) + KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.trace") ) fun renderErrorNode(error: String) = 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 0df08a08060..a405e3e647f 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 @@ -5,11 +5,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 import com.intellij.openapi.actionSystem.ActionManager @@ -36,6 +32,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider +import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder 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.CreationCoroutineStackFrameItem @@ -214,18 +211,14 @@ class XCoroutineView(val project: Project, val session: XDebugSession) : override fun computeChildren(node: XCompositeNode) { managerThreadExecutor.on(suspendContext).schedule { - val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext) val children = XValueChildrenList() - var stackFrames = debugProbesProxy.frameBuilder().build(infoData) - val creationStack = mutableListOf() - for (frame in stackFrames) { - if (frame is CreationCoroutineStackFrameItem) - creationStack.add(frame) - else - children.add(CoroutineFrameValue(infoData, frame)) + val doubleFrameList = CoroutineFrameBuilder.build(infoData, suspendContext) + doubleFrameList?.stackTrace?.forEach { + children.add(CoroutineFrameValue(infoData, it)) + } + doubleFrameList?.creationStackTrace?.let { + children.add(CreationFramesContainer(infoData, it)) } - if (creationStack.isNotEmpty()) - children.add(CreationFramesContainer(infoData, creationStack)) node.addChildren(children, true) } } 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 be95bc1dc7c..648f4ec6e5f 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 @@ -8,8 +8,6 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.view import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.JavaDebugProcess import com.intellij.debugger.engine.JavaExecutionStack -import com.intellij.debugger.engine.SuspendContextImpl -import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl import com.intellij.ui.DoubleClickListener import com.intellij.xdebugger.XDebugSession @@ -17,12 +15,11 @@ import com.intellij.xdebugger.frame.XExecutionStack import com.intellij.xdebugger.frame.XStackFrame import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl -import com.sun.jdi.ObjectReference import org.jetbrains.kotlin.idea.debugger.coroutine.data.* import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor -import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder -import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.findPosition -import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.suspendContextImpl +import org.jetbrains.kotlin.idea.debugger.coroutine.util.findPosition +import org.jetbrains.kotlin.idea.debugger.coroutine.util.invokeInManagerThread +import org.jetbrains.kotlin.idea.debugger.coroutine.util.suspendContextImpl import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread import java.awt.event.KeyAdapter import java.awt.event.KeyEvent @@ -61,11 +58,10 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD is RunningCoroutineStackFrameItem -> { val threadProxy = stackFrameItem.frame.threadProxy() val isCurrentContext = suspendContext.thread == threadProxy - val executionStack = JavaExecutionStack( + val executionStack = suspendContext.invokeInManagerThread { JavaExecutionStack( threadProxy, debugProcess, - isCurrentContext - ) + isCurrentContext) } ?: return false createStackAndSetFrame(threadProxy, { executionStack.createStackFrame(stackFrameItem.frame) }, isCurrentContext) } is CreationCoroutineStackFrameItem -> { @@ -73,15 +69,17 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD val threadProxy = suspendContext.thread ?: return false createStackAndSetFrame(threadProxy, { val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null - SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), emptyList(), position) + SyntheticStackFrame(stackFrameItem.descriptor(realFrame), emptyList(), position) }) } is SuspendCoroutineStackFrameItem -> { val threadProxy = suspendContext.thread ?: return false - val lastFrame = valueContainer.infoData.lastObservedFrameFieldRef ?: return false + val position = stackFrameItem.location.findPosition(session.project) + ?: return false + createStackAndSetFrame(threadProxy, { val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null - createSyntheticStackFrame(suspendContext, stackFrameItem, realFrame, lastFrame) + SyntheticStackFrame(stackFrameItem.descriptor(realFrame), stackFrameItem.spilledVariables, position) }) } is RestoredCoroutineStackFrameItem -> { @@ -89,16 +87,21 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD val position = stackFrameItem.location.findPosition(session.project) ?: return false createStackAndSetFrame(threadProxy, { - SyntheticStackFrame(stackFrameItem.emptyDescriptor(), stackFrameItem.spilledVariables, position) + SyntheticStackFrame(stackFrameItem.descriptor(), stackFrameItem.spilledVariables, position) }) } - is DefaultCoroutineStackFrameItem -> { + is DefaultCoroutineStackFrameItem, is SuspendCoroutineStackFrameItem -> { val threadProxy = suspendContext.thread ?: return false val position = stackFrameItem.location.findPosition(session.project) ?: return false createStackAndSetFrame(threadProxy, { val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null - SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), stackFrameItem.spilledVariables, position) + val descriptor = when (stackFrameItem) { + is DefaultCoroutineStackFrameItem -> stackFrameItem.descriptor(realFrame) + is SuspendCoroutineStackFrameItem -> stackFrameItem.descriptor(realFrame) + else -> null + } ?: return@createStackAndSetFrame null + SyntheticStackFrame(descriptor, stackFrameItem.spilledVariables, position) }) } else -> { @@ -124,39 +127,18 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD fun setCurrentStackFrame(stackFrameStack: XStackFrameStack) { applicationThreadExecutor.schedule( { - session.setCurrentStackFrame(stackFrameStack.executionStack, stackFrameStack.stackFrame) + session.setCurrentStackFrame(stackFrameStack.executionStack, stackFrameStack.stackFrame, false) }, tree ) } - data class XStackFrameStack(val stackFrame: XStackFrame, val executionStack: XExecutionStack); + data class XStackFrameStack(val stackFrame: XStackFrame, val executionStack: XExecutionStack) private fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false): XExecutionStack { val executionStack = JavaExecutionStack(proxy, debugProcess, isCurrentContext) executionStack.initTopFrame() return executionStack } - - - private fun createSyntheticStackFrame( - suspendContext: SuspendContextImpl, - frame: SuspendCoroutineStackFrameItem, - topFrame: StackFrameProxyImpl, - initialContinuation: ObjectReference - ): SyntheticStackFrame? { - val position = - applicationThreadExecutor.readAction { frame.stackTraceElement.findPosition(session.project) } - ?: return null - val continuation = - ContinuationHolder.lookup(suspendContext, initialContinuation) - ?: return null - - return SyntheticStackFrame( - frame.emptyDescriptor(topFrame), - continuation.getSpilledVariables() ?: return null, - position - ) - } } data class KeyMouseEvent(val keyEvent: KeyEvent?, val mouseEvent: MouseEvent?) { 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 f6fbbfe16d1..140929f393b 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 @@ -9,10 +9,9 @@ import com.intellij.debugger.engine.AsyncStackTraceProvider 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 org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider +import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder +import org.jetbrains.kotlin.idea.debugger.coroutine.util.isPreFlight import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.getSafe import java.io.PrintWriter import java.io.StringWriter @@ -26,18 +25,15 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin } override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) { - val asyncStackTraceProvider = getAsyncStackTraceProvider() - if (asyncStackTraceProvider == null) { - finish() - return - } - doOnBreakpoint { val frameProxy = this.frameProxy if (frameProxy != null) { try { - val coroutineInfoData = - asyncStackTraceProvider.lookupForResumeContinuation(frameProxy, this, emptyList())?.coroutineInfoData + val sem = frameProxy.location().isPreFlight() + val coroutineInfoData = if (sem.isCoroutineFound()) + CoroutineFrameBuilder.lookupContinuation(this, frameProxy, sem)?.coroutineInfoData + else + null if (coroutineInfoData != null && coroutineInfoData.stackTrace.isNotEmpty()) { print(renderAsyncStackTrace(coroutineInfoData.stackTrace), ProcessOutputTypes.SYSTEM) } else { @@ -56,23 +52,6 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin } } - private fun getAsyncStackTraceProvider(): CoroutineAsyncStackTraceProvider? { - val area = Extensions.getArea(null) - if (!area.hasExtensionPoint(ASYNC_STACKTRACE_EP_NAME)) { - System.err.println("$ASYNC_STACKTRACE_EP_NAME extension point is not found (probably old IDE version)") - return null - } - - val extensionPoint = area.getExtensionPoint(ASYNC_STACKTRACE_EP_NAME) - val provider = extensionPoint.extensions.firstIsInstanceOrNull() - - if (provider == null) { - System.err.println("Kotlin coroutine async stack trace provider is not found") - } - - return provider - } - private fun Throwable.stackTraceAsString(): String { val writer = StringWriter() printStackTrace(PrintWriter(writer)) diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractContinuationStackTraceTest.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractContinuationStackTraceTest.kt new file mode 100644 index 00000000000..6096a5ad39a --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractContinuationStackTraceTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.debugger.test + +import com.intellij.debugger.engine.AsyncStackTraceProvider +import com.intellij.debugger.engine.JavaStackFrame +import com.intellij.debugger.engine.SuspendContextImpl +import com.intellij.debugger.impl.OutputChecker +import com.intellij.debugger.memory.utils.StackFrameItem +import com.intellij.execution.process.ProcessOutputTypes +import com.intellij.openapi.extensions.Extensions +import com.intellij.xdebugger.frame.* +import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider +import org.jetbrains.kotlin.idea.debugger.coroutine.util.format +import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread +import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences +import org.jetbrains.kotlin.idea.debugger.test.util.KotlinOutputChecker +import org.jetbrains.kotlin.idea.debugger.test.util.XDebuggerTestUtil +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.io.PrintWriter +import java.io.StringWriter + +abstract class AbstractContinuationStackTraceTest : KotlinDescriptorTestCaseWithStackFrames() { + override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) { + val asyncStackTraceProvider = getAsyncStackTraceProvider() + + doWhenXSessionPausedThenResume { + printContext(debugProcess.debuggerContext) + val suspendContext = debuggerSession.xDebugSession?.getSuspendContext() + var executionStack = suspendContext?.getActiveExecutionStack() + if (executionStack != null) { + try { + out("Thread stack trace:") + val stackFrames: List = XDebuggerTestUtil.collectFrames(executionStack) + for (frame in stackFrames) { + if (frame is JavaStackFrame) { + out(frame) + asyncStackTraceProvider?.getAsyncStackTrace(frame, suspendContext as SuspendContextImpl)?.let { + for (frameItem in it) + out(frameItem) + return@doWhenXSessionPausedThenResume + } + } + } + } catch (e: Throwable) { + val stackTrace = e.stackTraceAsString() + System.err.println("Exception occurred on calculating async stack traces: $stackTrace") + throw e + } + } else { + println("FrameProxy is 'null', can't calculate async stack trace", ProcessOutputTypes.SYSTEM) + } + } + } +} diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractXCoroutinesStackTraceTest.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractXCoroutinesStackTraceTest.kt new file mode 100644 index 00000000000..7f930080a73 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractXCoroutinesStackTraceTest.kt @@ -0,0 +1,47 @@ +/* + * 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.test + +import com.intellij.debugger.engine.JavaStackFrame +import com.intellij.debugger.engine.SuspendContextImpl +import com.intellij.execution.process.ProcessOutputTypes +import com.intellij.xdebugger.frame.XStackFrame +import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences +import org.jetbrains.kotlin.idea.debugger.test.util.XDebuggerTestUtil + +abstract class AbstractXCoroutinesStackTraceTest : KotlinDescriptorTestCaseWithStackFrames() { + override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) { + val asyncStackTraceProvider = getAsyncStackTraceProvider() + + doWhenXSessionPausedThenResume { + printContext(debugProcess.debuggerContext) + val suspendContext = debuggerSession.xDebugSession?.getSuspendContext() + var executionStack = suspendContext?.getActiveExecutionStack() + if (executionStack != null) { + try { + out("Thread stack trace:") + val stackFrames: List = XDebuggerTestUtil.collectFrames(executionStack) + for (frame in stackFrames) { + if (frame is JavaStackFrame) { + out(frame) + asyncStackTraceProvider?.getAsyncStackTrace(frame, suspendContext as SuspendContextImpl)?.let { + for (frameItem in it) + out(frameItem) + return@doWhenXSessionPausedThenResume + } + } + } + } catch (e: Throwable) { + val stackTrace = e.stackTraceAsString() + System.err.println("Exception occurred on calculating async stack traces: $stackTrace") + throw e + } + } else { + println("FrameProxy is 'null', can't calculate async stack trace", ProcessOutputTypes.SYSTEM) + } + } + } +} diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/ContinuationStackTraceTestGenerated.java b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/ContinuationStackTraceTestGenerated.java new file mode 100644 index 00000000000..b2ea65b1527 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/ContinuationStackTraceTestGenerated.java @@ -0,0 +1,40 @@ +/* + * 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.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/jvm-debugger/jvm-debugger-test/testData/continuation") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ContinuationStackTraceTestGenerated extends AbstractContinuationStackTraceTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInContinuation() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/jvm-debugger/jvm-debugger-test/testData/continuation"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("suspendFun.kt") + public void testSuspendFun() throws Exception { + runTest("idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt"); + } + + @TestMetadata("suspendLambda.kt") + public void testSuspendLambda() throws Exception { + runTest("idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt"); + } +} diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/DebuggerTestCompilerFacility.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/DebuggerTestCompilerFacility.kt index 350102b29cd..c8985e18e1c 100644 --- a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/DebuggerTestCompilerFacility.kt +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/DebuggerTestCompilerFacility.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.roots.LibraryOrderEntry import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.libraries.ui.OrderRoot import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile @@ -39,6 +40,7 @@ class DebuggerTestCompilerFacility(files: List, private val jvmTarget: private val mainFiles: TestFilesByLanguage private val libraryFiles: TestFilesByLanguage + private val mavenArtifacts = mutableListOf() init { val splitFiles = splitByTarget(files) @@ -61,6 +63,15 @@ class DebuggerTestCompilerFacility(files: List, private val jvmTarget: compileLibrary(libraryFiles, srcDir, classesDir) } + fun addDependencies(libraryPaths: List) { + for (libraryPath in libraryPaths) { + mavenArtifacts.add(libraryPath) + } + } + + fun kotlinStdlibInMavenArtifacts() = + mavenArtifacts.find { it.contains(Regex("""kotlin-stdlib-\d+\.\d+\.\d+(\-\w+)?""")) } + fun compileLibrary(srcDir: File, classesDir: File) { compileLibrary(this.libraryFiles, srcDir, classesDir) @@ -72,19 +83,22 @@ class DebuggerTestCompilerFacility(files: List, private val jvmTarget: resources.copy(classesDir) (kotlin + java).copy(srcDir) + if (kotlinStdlibInMavenArtifacts() == null) + mavenArtifacts.add(kotlinStdlibPath) + if (kotlin.isNotEmpty()) { MockLibraryUtil.compileKotlin( srcDir.absolutePath, classesDir, listOf("-jvm-target", jvmTarget.description), - kotlinStdlibPath + *(mavenArtifacts.toTypedArray()) ) } if (java.isNotEmpty()) { CodegenTestUtil.compileJava( java.map { File(srcDir, it.name).absolutePath }, - listOf(kotlinStdlibPath, classesDir.absolutePath), + mavenArtifacts + classesDir.absolutePath, listOf("-g"), classesDir ) diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCase.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCase.kt index 4edfb2641b8..4efeab34052 100644 --- a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCase.kt +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCase.kt @@ -9,6 +9,8 @@ import com.intellij.debugger.impl.DescriptorTestCase import com.intellij.debugger.impl.OutputChecker import com.intellij.execution.configurations.JavaParameters import com.intellij.execution.process.ProcessOutputTypes +import com.intellij.jarRepository.JarRepositoryManager +import com.intellij.jarRepository.RemoteRepositoryDescription import com.intellij.openapi.roots.LibraryOrderEntry import com.intellij.openapi.roots.ModifiableRootModel import com.intellij.openapi.roots.ModuleRootManager @@ -21,6 +23,7 @@ import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.PsiFile import com.intellij.testFramework.EdtTestUtil import com.intellij.xdebugger.XDebugSession +import org.jetbrains.idea.maven.aether.ArtifactKind import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches @@ -38,6 +41,7 @@ import org.jetbrains.kotlin.test.isIgnoredInDatabaseWithLog import org.jetbrains.kotlin.test.testFramework.runWriteAction import org.junit.ComparisonFailure import java.io.File +import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor as JpsMavenRepositoryLibraryDescriptor internal const val KOTLIN_LIBRARY_NAME = "KotlinLibrary" internal const val TEST_LIBRARY_NAME = "TestLibrary" @@ -109,7 +113,10 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() { val compilerFacility = DebuggerTestCompilerFacility(testFiles, jvmTarget) for (library in preferences[DebuggerPreferenceKeys.ATTACH_LIBRARY]) { - compilerFacility.compileExternalLibrary(library, librarySrcDirectory, libraryOutputDirectory) + if (library.startsWith("maven(")) + addMavenDependency(compilerFacility, library) + else + compilerFacility.compileExternalLibrary(library, librarySrcDirectory, libraryOutputDirectory) } compilerFacility.compileLibrary(librarySrcDirectory, libraryOutputDirectory) @@ -125,6 +132,9 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() { doMultiFileTest(testFiles, preferences) } + open fun addMavenDependency(compilerFacility: DebuggerTestCompilerFacility, library: String) { + } + private fun createTestFiles(wholeFile: File, wholeFileContents: String): TestFiles { val testFiles = org.jetbrains.kotlin.test.TestFiles.createTestFiles( wholeFile.name, @@ -227,6 +237,5 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() { return super.shouldRunTest() && !isIgnoredInDatabaseWithLog(this) } - private fun getTestDirectoryPath(): String = javaClass.getAnnotation(TestMetadata::class.java).value + protected fun getTestDirectoryPath(): String = javaClass.getAnnotation(TestMetadata::class.java).value } - diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt new file mode 100644 index 00000000000..e399e850599 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt @@ -0,0 +1,146 @@ +/* + * 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.test + +import com.intellij.debugger.engine.AsyncStackTraceProvider +import com.intellij.debugger.engine.JavaStackFrame +import com.intellij.debugger.memory.utils.StackFrameItem +import com.intellij.execution.configurations.JavaParameters +import com.intellij.execution.process.ProcessOutputTypes +import com.intellij.jarRepository.JarRepositoryManager +import com.intellij.jarRepository.RemoteRepositoryDescription +import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.roots.libraries.ui.OrderRoot +import com.intellij.openapi.roots.ui.configuration.libraryEditor.NewLibraryEditor +import com.intellij.testFramework.EdtTestUtil +import com.intellij.xdebugger.frame.XNamedValue +import com.intellij.xdebugger.frame.XStackFrame +import org.jetbrains.idea.maven.aether.ArtifactKind +import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor +import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider +import org.jetbrains.kotlin.idea.debugger.coroutine.data.CreationCoroutineStackFrameItem +import org.jetbrains.kotlin.idea.debugger.coroutine.util.format +import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread +import org.jetbrains.kotlin.idea.debugger.test.util.XDebuggerTestUtil +import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil +import org.jetbrains.kotlin.test.testFramework.runWriteAction +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.io.PrintWriter +import java.io.StringWriter + +abstract class KotlinDescriptorTestCaseWithStackFrames() : KotlinDescriptorTestCaseWithStepping() { + private companion object { + val ASYNC_STACKTRACE_EP_NAME = AsyncStackTraceProvider.EP.name + val INDENT_FRAME = 1 + val INDENT_VARIABLES = 2 + } + + val agentList = mutableListOf() + val classPath = mutableListOf() + + protected fun out(stackFrame: StackFrameItem) { + if (stackFrame is CreationCoroutineStackFrameItem && stackFrame.first) + out(0, "Creation stack frame") + out(INDENT_FRAME, stackFrame.format()) + outVariables(debugProcess.invokeInManagerThread { stackFrame.createFrame(debugProcess) } ?: return) + } + + protected fun out(frame: JavaStackFrame) { + out(INDENT_FRAME, XDebuggerTestUtil.getFramePresentation(frame)) + outVariables(frame) + } + + private fun outVariables(stackFrame: XStackFrame) { + val variables = XDebuggerTestUtil.collectChildrenWithError(stackFrame) + val sorted = mutableListOf() + sorted.addAll(variables.first.mapNotNull { if (it is XNamedValue) it.name else null }) + sorted.sort() + val varString = sorted.joinToString() + out(INDENT_VARIABLES, "($varString)") + } + + protected fun out(text: String) { + println(text, ProcessOutputTypes.SYSTEM) + } + + protected fun out(indent: Int, text: String) { + println("\t".repeat(indent) + text, ProcessOutputTypes.SYSTEM) + println(text) + } + + protected fun Throwable.stackTraceAsString(): String { + val writer = StringWriter() + printStackTrace(PrintWriter(writer)) + return writer.toString() + } + + protected fun getAsyncStackTraceProvider(): CoroutineAsyncStackTraceProvider? { + val area = Extensions.getArea(null) + if (!area.hasExtensionPoint(ASYNC_STACKTRACE_EP_NAME)) { + System.err.println("${ASYNC_STACKTRACE_EP_NAME} extension point is not found (probably old IDE version)") + return null + } + + val extensionPoint = area.getExtensionPoint(ASYNC_STACKTRACE_EP_NAME) + val provider = extensionPoint.extensions.firstIsInstanceOrNull() + + if (provider == null) { + System.err.println("Kotlin coroutine async stack trace provider is not found") + } + return provider + } + + override fun addMavenDependency(compilerFacility: DebuggerTestCompilerFacility, library: String) { + val regex = Regex(pattern = """maven\(([a-zA-Z0-9_\-\.]+)\:([a-zA-Z0-9_\-\.]+):([a-zA-Z0-9_\-\.]+)\)(\-javaagent)?""") + val result = regex.matchEntire(library) ?: return + val (_, groupId: String, artifactId: String, version: String, agent: String) = result.groupValues + if ("-javaagent" == agent) + agentList.add(JpsMavenRepositoryLibraryDescriptor(groupId, artifactId, version, false)) + val description = JpsMavenRepositoryLibraryDescriptor(groupId, artifactId, version) + val artifacts = loadDependencies(description) + compilerFacility.addDependencies(artifacts.map { it.file.presentableUrl }) + addLibraries(artifacts) + } + + override fun createJavaParameters(mainClass: String?): JavaParameters { + val params = super.createJavaParameters(mainClass) + for (entry in classPath) { + params.classPath.add(entry) + } + for (agent in agentList) { + val dependencies = loadDependencies(agent) + for (dependency in dependencies) { + params.vmParametersList.add("-javaagent:${dependency.file.presentableUrl}") + } + } + return params + } + + private fun addLibraries(artifacts: MutableList) = + EdtTestUtil.runInEdtAndWait { + runWriteAction { + val model = ModuleRootManager.getInstance(myModule).modifiableModel + val customLibEditor = NewLibraryEditor().apply { + for (artifact in artifacts) { + classPath.add(artifact.file.presentableUrl) // for sandbox jvm + addRoot(artifact.file, artifact.type) + } + } + ConfigLibraryUtil.addLibrary(customLibEditor, model, null) // for kotlin compiler + model.commit() + } + } + + private fun loadDependencies( + description: JpsMavenRepositoryLibraryDescriptor + ): MutableList { + return JarRepositoryManager.loadDependenciesSync( + project, description, setOf(ArtifactKind.ARTIFACT), + RemoteRepositoryDescription.DEFAULT_REPOSITORIES, null + ) ?: throw AssertionError("Maven Dependency not found: $description") + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/XCoroutinesStackTraceTestGenerated.java b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/XCoroutinesStackTraceTestGenerated.java new file mode 100644 index 00000000000..216ec8e0e5a --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/XCoroutinesStackTraceTestGenerated.java @@ -0,0 +1,35 @@ +/* + * 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.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class XCoroutinesStackTraceTestGenerated extends AbstractXCoroutinesStackTraceTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInXcoroutines() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("coroutineSuspendFun.kt") + public void testCoroutineSuspendFun() throws Exception { + runTest("idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt"); + } +} diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XDebuggerTestUtil.java b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XDebuggerTestUtil.java new file mode 100644 index 00000000000..730ce83a652 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XDebuggerTestUtil.java @@ -0,0 +1,115 @@ +/* + * 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.test.util; + +import com.intellij.openapi.util.Pair; +import com.intellij.util.ui.TextTransferable; +import com.intellij.xdebugger.frame.XExecutionStack; +import com.intellij.xdebugger.frame.XStackFrame; +import com.intellij.xdebugger.frame.XValue; +import com.intellij.xdebugger.frame.XValueContainer; +import com.intellij.xdebugger.impl.frame.XStackFrameContainerEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; + +import static org.junit.Assert.assertNull; + +/** + * Kotlin clone of com.intellij.xdebugger.XDebuggerTestUtil + */ +public class XDebuggerTestUtil { + public static final int TIMEOUT_MS = 25_000; + + public static List collectFrames(@NotNull XExecutionStack thread) { + return collectFrames(thread, TIMEOUT_MS * 2); + } + + public static List collectFrames(XExecutionStack thread, long timeout) { + return collectFrames(thread, timeout, XDebuggerTestUtil::waitFor); + } + + public static List collectFrames(XExecutionStack thread, long timeout, BiFunction waitFunction) { + return collectFramesWithError(thread, timeout, waitFunction).first; + } + + public static String getFramePresentation(XStackFrame frame) { + TextTransferable.ColoredStringBuilder builder = new TextTransferable.ColoredStringBuilder(); + frame.customizePresentation(builder); + return builder.getBuilder().toString(); + } + + public static Pair, String> collectFramesWithError(XExecutionStack thread, long timeout, BiFunction waitFunction) { + XTestStackFrameContainer container = new XTestStackFrameContainer(); + thread.computeStackFrames(0, container); + return container.waitFor(timeout, waitFunction); + } + + public static boolean waitFor(Semaphore semaphore, long timeoutInMillis) { + long end = System.currentTimeMillis() + timeoutInMillis; + long remaining = timeoutInMillis; + do { + try { + return semaphore.tryAcquire(remaining, TimeUnit.MILLISECONDS); + } + catch (InterruptedException ignored) { + remaining = end - System.currentTimeMillis(); + } + } while (remaining > 0); + return false; + } + + public static class XTestStackFrameContainer extends XTestContainer implements XStackFrameContainerEx { + public volatile XStackFrame frameToSelect; + + @Override + public void addStackFrames(@NotNull List stackFrames, boolean last) { + addChildren(stackFrames, last); + } + + @Override + public void addStackFrames(@NotNull List stackFrames, @Nullable XStackFrame toSelect, boolean last) { + if (toSelect != null) frameToSelect = toSelect; + addChildren(stackFrames, last); + } + + @Override + public void errorOccurred(@NotNull String errorMessage) { + setErrorMessage(errorMessage); + } + } + + @NotNull + public static List collectChildren(XValueContainer value) { + return collectChildren(value, XDebuggerTestUtil::waitFor); + } + + @NotNull + public static List collectChildren(XValueContainer value, BiFunction waitFunction) { + final Pair, String> childrenWithError = collectChildrenWithError(value, waitFunction); + final String error = childrenWithError.second; + assertNull("Error getting children: " + error, error); + return childrenWithError.first; + } + + @NotNull + public static Pair, String> collectChildrenWithError(XValueContainer value) { + return collectChildrenWithError(value, XDebuggerTestUtil::waitFor); + } + + @NotNull + public static Pair, String> collectChildrenWithError(XValueContainer value, + BiFunction waitFunction) { + XTestCompositeNode container = new XTestCompositeNode(); + value.computeChildren(container); + + return container.waitFor(TIMEOUT_MS, waitFunction); + } +} diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestCompositeNode.java b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestCompositeNode.java new file mode 100644 index 00000000000..9986969ed67 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestCompositeNode.java @@ -0,0 +1,29 @@ +/* + * 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.test.util; + +import com.intellij.xdebugger.frame.XCompositeNode; +import com.intellij.xdebugger.frame.XValue; +import com.intellij.xdebugger.frame.XValueChildrenList; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class XTestCompositeNode extends XTestContainer implements XCompositeNode { + @Override + public void addChildren(@NotNull XValueChildrenList children, boolean last) { + final List list = new ArrayList<>(); + for (int i = 0; i < children.size(); i++) { + list.add(children.getValue(i)); + } + addChildren(list, last); + } + + @Override + public void setAlreadySorted(boolean alreadySorted) { + } +} diff --git a/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestContainer.java b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestContainer.java new file mode 100644 index 00000000000..a757391e9c3 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/test/org/jetbrains/kotlin/idea/debugger/test/util/XTestContainer.java @@ -0,0 +1,59 @@ +/* + * 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.test.util; + +import com.intellij.openapi.util.Pair; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.SmartList; +import com.intellij.xdebugger.frame.XDebuggerTreeNodeHyperlink; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.function.BiFunction; + +public class XTestContainer { + private final List myChildren = new SmartList<>(); + private String myErrorMessage; + private final Semaphore myFinished = new Semaphore(0); + + public void addChildren(List children, boolean last) { + myChildren.addAll(children); + if (last) myFinished.release(); + } + + public void tooManyChildren(int remaining) { + myFinished.release(); + } + + public void setMessage(@NotNull String message, Icon icon, @NotNull final SimpleTextAttributes attributes, @Nullable XDebuggerTreeNodeHyperlink link) { + } + + public void setErrorMessage(@NotNull String message, @Nullable XDebuggerTreeNodeHyperlink link) { + setErrorMessage(message); + } + + public void setErrorMessage(@NotNull String errorMessage) { + myErrorMessage = errorMessage; + myFinished.release(); + } + + @NotNull + public Pair, String> waitFor(long timeoutMs) { + return waitFor(timeoutMs, (semaphore, timeout) -> XDebuggerTestUtil.waitFor(myFinished, timeout)); + } + + @NotNull + public Pair, String> waitFor(long timeoutMs, BiFunction waitFunction) { + if (!waitFunction.apply(myFinished, timeoutMs)) { + throw new AssertionError("Waiting timed out"); + } + + return Pair.create(myChildren, myErrorMessage); + } +} diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt new file mode 100644 index 00000000000..00bd1928d30 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt @@ -0,0 +1,30 @@ +package coroutine1 + +import kotlin.coroutines.Continuation +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.coroutines.startCoroutine + +fun main() { + val cnt = Continuation(EmptyCoroutineContext) { } + val result = ::test1.startCoroutine(1, cnt) + println(result) +} + +suspend fun test1(i: Int): Int { + val test1 = "a" + a(test1) + println(test1) + return i +} + +suspend fun a(aParam: String) { + val a = "a" + b(a) + println(a) +} + +suspend fun b(bParam: String) { + val b = "b" + // Breakpoint! + println(b) +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt new file mode 100644 index 00000000000..a79d1b427a0 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt @@ -0,0 +1,26 @@ +package continuation + +fun main() { + val a = "a" + fibonacci().take(10).toList() +} + +fun nextSequence(terms: Pair): Pair { + val terms1 = Pair(terms.second, terms.first + terms.second) + if (terms1.first == 8) { + //Breakpoint! + return terms1 + } else + return terms1 +} + +fun fibonacci() = sequence { + var terms = Pair(0, 1) + var step = 0 + + while (true) { + yield(terms.first) + terms = nextSequence(terms) + step++ + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out new file mode 100644 index 00000000000..0cbe27833d3 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out @@ -0,0 +1,28 @@ +LineBreakpoint created at sequence.kt:14 +Run Java +Connected to the target VM +sequence.kt:12 +Thread stack trace: + nextSequence:12, SequenceKt (continuation) + (terms, terms1) + invokeSuspend:23, SequenceKt$fibonacci$1 (continuation) + ($result, $this$sequence, step, terms, this) + resumeWith:33, kotlin.coroutines.jvm.internal.BaseContinuationImpl + ($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this) + hasNext:140, kotlin.sequences.SequenceBuilderIterator + (step, this) + hasNext:374, kotlin.sequences.TakeSequence$iterator$1 + (this) + toCollection:722, kotlin.sequences.SequencesKt___SequencesKt + ($this$toCollection, destination) + toMutableList:752, kotlin.sequences.SequencesKt___SequencesKt + ($this$toMutableList) + toList:743, kotlin.sequences.SequencesKt___SequencesKt + ($this$toList) + main:5, continuation.SequenceKt + (a) + main:-1, continuation.SequenceKt + () +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt new file mode 100644 index 00000000000..adb13ac1031 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt @@ -0,0 +1,30 @@ +package continuation +// ATTACH_LIBRARY: coroutines +// ATTACH_LIBRARY: maven(org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.4)-javaagent + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield + +fun main() { + val main = "main" + runBlocking { + a() + } +} + +suspend fun a() { + val a = "a" + b(a) + val aLate = "a" // to prevent stackFrame to collapse +} + +suspend fun b(paramA: String) { + yield() + val b = "b" + c(b) +} + +suspend fun c(paramB: String) { + val c = "c" + //Breakpoint! +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out new file mode 100644 index 00000000000..41260aaa1ac --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out @@ -0,0 +1,47 @@ +LineBreakpoint created at coroutineSuspendFun.kt:30 +Run Java +Connected to the target VM +coroutineSuspendFun.kt:30 +Thread stack trace: + c:30, CoroutineSuspendFunKt (continuation) + ($completion, c, paramB) + b:24, CoroutineSuspendFunKt (continuation) + ($completion, $continuation, $result, b, paramA) + a:17, continuation.CoroutineSuspendFunKt + (a) + invokeSuspend:11, continuation.CoroutineSuspendFunKt$main$1 + ($this$runBlocking) + resumeWith:33, kotlin.coroutines.jvm.internal.BaseContinuationImpl + ($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this) + run:56, kotlinx.coroutines.DispatchedTask + ($i$a$-withCoroutineContext-DispatchedTask$run$1, $i$f$withCoroutineContext, context, continuation, countOrElement$iv, delegate, exception, fatalException, job, oldValue$iv, state, taskContext, this) + processNextEvent:272, kotlinx.coroutines.EventLoopImplBase + (delayed, this) + joinBlocking:79, kotlinx.coroutines.BlockingCoroutine + (this) + runBlocking:54, kotlinx.coroutines.BuildersKt__BuildersKt + (block, context, contextInterceptor, coroutine, currentThread, eventLoop, newContext) + runBlocking:1, kotlinx.coroutines.BuildersKt + () + runBlocking$default:36, kotlinx.coroutines.BuildersKt__BuildersKt + () + runBlocking$default:1, kotlinx.coroutines.BuildersKt + () + main:10, continuation.CoroutineSuspendFunKt + (main) + main:-1, continuation.CoroutineSuspendFunKt + () +Creation stack frame + createCoroutineUnintercepted:116, kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt + () + startCoroutineCancellable:26, kotlinx.coroutines.intrinsics.CancellableKt + () + runBlocking$default:1, kotlinx.coroutines.BuildersKt + () + main:10, continuation.CoroutineSuspendFunKt + () + main:-1, continuation.CoroutineSuspendFunKt + () +Disconnected from the target VM + +Process finished with exit code 0