(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
This commit is contained in:
@@ -240,6 +240,14 @@ fun main(args: Array<String>) {
|
|||||||
// TODO: implement mapping logic for terminal operations
|
// TODO: implement mapping logic for terminal operations
|
||||||
model("sequence/streams/sequence", excludeDirs = listOf("terminal"))
|
model("sequence/streams/sequence", excludeDirs = listOf("terminal"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testClass<AbstractContinuationStackTraceTest> {
|
||||||
|
model("continuation")
|
||||||
|
}
|
||||||
|
|
||||||
|
testClass<AbstractXCoroutinesStackTraceTest> {
|
||||||
|
model("xcoroutines")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testGroup("idea/tests", "idea/testData") {
|
testGroup("idea/tests", "idea/testData") {
|
||||||
|
|||||||
-1
@@ -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.full.copied=Full coroutine dump was successfully copied to clipboard
|
||||||
|
|
||||||
coroutine.dump.creation.trace=Coroutine creation stack trace
|
coroutine.dump.creation.trace=Coroutine creation stack trace
|
||||||
coroutine.dump.creation.frame=Creation stack frame of {0}
|
|
||||||
|
|
||||||
coroutine.dump.threads.loading=Loading…
|
coroutine.dump.threads.loading=Loading…
|
||||||
|
|
||||||
|
|||||||
+14
-40
@@ -8,21 +8,19 @@ package org.jetbrains.kotlin.idea.debugger.coroutine
|
|||||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||||
import com.intellij.debugger.engine.JavaStackFrame
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
|
||||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutinePreflightStackFrame
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.*
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.PreCoroutineStackFrameItem
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
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.hopelessAware
|
||||||
import org.jetbrains.kotlin.idea.debugger.isInKotlinSources
|
|
||||||
|
|
||||||
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||||
|
|
||||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||||
val stackFrameList = hopelessAware {
|
val stackFrameList = hopelessAware {
|
||||||
if (stackFrame is CoroutinePreflightStackFrame)
|
if (stackFrame is CoroutinePreflightStackFrame)
|
||||||
lookupForAfterPreflight(stackFrame, suspendContext)
|
processPreflight(stackFrame, suspendContext)
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -31,45 +29,21 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
|||||||
else stackFrameList
|
else stackFrameList
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lookupForAfterPreflight(
|
private fun processPreflight(
|
||||||
stackFrame: CoroutinePreflightStackFrame,
|
preflightFrame: CoroutinePreflightStackFrame,
|
||||||
suspendContext: SuspendContextImpl
|
suspendContext: SuspendContextImpl
|
||||||
): List<CoroutineStackFrameItem>? {
|
): List<CoroutineStackFrameItem>? {
|
||||||
val resumeWithFrame = stackFrame.threadPreCoroutineFrames.firstOrNull()
|
val resumeWithFrame = preflightFrame.threadPreCoroutineFrames.firstOrNull()
|
||||||
|
|
||||||
if (threadAndContextSupportsEvaluation(suspendContext, resumeWithFrame)) {
|
if (threadAndContextSupportsEvaluation(
|
||||||
val stackFrames = mutableListOf<CoroutineStackFrameItem>()
|
suspendContext,
|
||||||
stackFrames.addAll(stackFrame.coroutineInfoData.stackTrace)
|
resumeWithFrame
|
||||||
|
)
|
||||||
// val lastRestoredFrame = stackFrame.coroutineInfoData.stackTrace.first()
|
) {
|
||||||
|
val doubleFrameList = CoroutineFrameBuilder.build(preflightFrame, suspendContext)
|
||||||
stackFrames.addAll(stackFrame.threadPreCoroutineFrames.mapIndexed { index, stackFrameProxyImpl ->
|
return doubleFrameList.stackTrace + doubleFrameList.creationStackTrace
|
||||||
// if (index == 0)
|
|
||||||
// PreCoroutineStackFrameItem(stackFrameProxyImpl, lastRestoredFrame) // get location and variables also from restored part
|
|
||||||
// else
|
|
||||||
PreCoroutineStackFrameItem(stackFrameProxyImpl)
|
|
||||||
})
|
|
||||||
stackFrames.addAll(stackFrame.coroutineInfoData.creationStackTrace)
|
|
||||||
return stackFrames
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lookupForResumeContinuation(
|
|
||||||
frameProxy: StackFrameProxyImpl,
|
|
||||||
suspendContext: SuspendContextImpl,
|
|
||||||
framesLeft: List<StackFrameProxyImpl>
|
|
||||||
): 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -12,14 +12,15 @@ import com.intellij.execution.configurations.RunnerSettings;
|
|||||||
import com.intellij.openapi.components.ServiceManager;
|
import com.intellij.openapi.components.ServiceManager;
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class CoroutineDebugConfigurationExtension extends RunConfigurationExtension {
|
public class CoroutineDebugConfigurationExtension extends RunConfigurationExtension {
|
||||||
private static final Logger log = Logger.getInstance(CoroutineDebugConfigurationExtension.class);
|
private static final Logger log = Logger.getInstance(CoroutineDebugConfigurationExtension.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends RunConfigurationBase> void updateJavaParameters(
|
public <T extends RunConfigurationBase> void updateJavaParameters(
|
||||||
T configuration, JavaParameters params, RunnerSettings runnerSettings
|
@NotNull T configuration, @NotNull JavaParameters params, RunnerSettings runnerSettings
|
||||||
) throws ExecutionException {
|
) {
|
||||||
if (configuration != null) {
|
if (configuration != null) {
|
||||||
Project project = configuration.getProject();
|
Project project = configuration.getProject();
|
||||||
DebuggerListener listener = ServiceManager.getService(project, DebuggerListener.class);
|
DebuggerListener listener = ServiceManager.getService(project, DebuggerListener.class);
|
||||||
@@ -31,7 +32,7 @@ public class CoroutineDebugConfigurationExtension extends RunConfigurationExtens
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isApplicableFor(RunConfigurationBase<?> configuration) {
|
public boolean isApplicableFor(@NotNull RunConfigurationBase<?> configuration) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-7
@@ -9,6 +9,8 @@ import com.intellij.debugger.actions.AsyncStacksToggleAction
|
|||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
|
import com.intellij.openapi.application.Application
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.xdebugger.frame.XStackFrame
|
import com.intellij.xdebugger.frame.XStackFrame
|
||||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
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.StackFrameInterceptor
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
|
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 {
|
class CoroutineStackFrameInterceptor(val project: Project) : StackFrameInterceptor {
|
||||||
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? =
|
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? {
|
||||||
if (debugProcess.xdebugProcess?.session is XDebugSessionImpl &&
|
val stackFrame = if (debugProcess.xdebugProcess?.session is XDebugSessionImpl
|
||||||
frame !is SkipCoroutineStackFrameProxyImpl &&
|
&& frame !is SkipCoroutineStackFrameProxyImpl
|
||||||
AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)
|
&& AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)
|
||||||
)
|
) {
|
||||||
ContinuationHolder.coroutineExitFrame(frame, debugProcess.debuggerContext.suspendContext)
|
val suspendContextImpl = when {
|
||||||
else
|
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
|
null
|
||||||
|
return stackFrame
|
||||||
|
}
|
||||||
}
|
}
|
||||||
-2
@@ -100,5 +100,3 @@ class DebuggerConnection(
|
|||||||
return ui.createContent(param.id, param.component, param.displayName, param.icon, param.parentComponent)
|
return ui.createContent(param.id, param.component, param.displayName, param.icon, param.parentComponent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun coroutineDebuggerTraceEnabled() = Registry.`is`("kotlin.debugger.coroutines.trace")
|
|
||||||
|
|||||||
-61
@@ -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<CoroutineStackFrameItem> {
|
|
||||||
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-2
@@ -10,12 +10,13 @@ import com.intellij.debugger.engine.evaluation.EvaluateException
|
|||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.sun.jdi.ObjectReference
|
||||||
import com.sun.jdi.Value
|
import com.sun.jdi.Value
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||||
|
|
||||||
class ContinuationValueDescriptorImpl(
|
class ContinuationValueDescriptorImpl(
|
||||||
project: Project,
|
project: Project,
|
||||||
val continuation: ContinuationHolder,
|
val continuation: ObjectReference,
|
||||||
val fieldName: String,
|
val fieldName: String,
|
||||||
val variableName: String
|
val variableName: String
|
||||||
) : ValueDescriptorImpl(project) {
|
) : ValueDescriptorImpl(project) {
|
||||||
@@ -23,7 +24,7 @@ class ContinuationValueDescriptorImpl(
|
|||||||
|
|
||||||
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
|
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
|
||||||
val field = continuation.referenceType()?.fieldByName(fieldName) ?: return null
|
val field = continuation.referenceType()?.fieldByName(fieldName) ?: return null
|
||||||
return continuation.field(field)
|
return continuation.getValue(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||||
|
|||||||
+40
-75
@@ -7,77 +7,57 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
|
|||||||
|
|
||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
|
||||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||||
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.XNamedValue
|
||||||
import com.intellij.xdebugger.frame.XStackFrame
|
|
||||||
import com.sun.jdi.Location
|
import com.sun.jdi.Location
|
||||||
import com.sun.jdi.ObjectReference
|
|
||||||
import com.sun.jdi.StackFrame
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.*
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isInvokeSuspend
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.coroutineDebuggerTraceEnabled
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
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(
|
class SuspendCoroutineStackFrameItem(
|
||||||
val stackTraceElement: StackTraceElement,
|
val stackTraceElement: StackTraceElement,
|
||||||
location: Location,
|
location: Location,
|
||||||
spilledVariables: List<XNamedValue> = emptyList()
|
spilledVariables: List<XNamedValue> = emptyList()
|
||||||
) : CoroutineStackFrameItem(location, spilledVariables) {
|
) : 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<XNamedValue> = 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(
|
class RestoredCoroutineStackFrameItem(
|
||||||
val frame: StackFrameProxyImpl,
|
val frame: StackFrameProxyImpl,
|
||||||
location: Location,
|
location: Location,
|
||||||
spilledVariables: List<XNamedValue>
|
spilledVariables: List<XNamedValue>
|
||||||
) : CoroutineStackFrameItem(location, spilledVariables) {
|
) : CoroutineStackFrameItem(location, spilledVariables) {
|
||||||
fun emptyDescriptor() =
|
|
||||||
|
fun descriptor() =
|
||||||
StackFrameDescriptorImpl(frame, MethodsTracker())
|
StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +67,7 @@ class RestoredCoroutineStackFrameItem(
|
|||||||
class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<XNamedValue>) :
|
class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<XNamedValue>) :
|
||||||
CoroutineStackFrameItem(location, spilledVariables) {
|
CoroutineStackFrameItem(location, spilledVariables) {
|
||||||
|
|
||||||
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
fun descriptor(frame: StackFrameProxyImpl) =
|
||||||
StackFrameDescriptorImpl(frame, MethodsTracker())
|
StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,10 +80,10 @@ class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<
|
|||||||
* - invokeSuspend(KotlinStackFrame) -|
|
* - invokeSuspend(KotlinStackFrame) -|
|
||||||
* | replaced with CoroutinePreflightStackFrame
|
* | replaced with CoroutinePreflightStackFrame
|
||||||
* - resumeWith(KotlinStackFrame) ----|
|
* - resumeWith(KotlinStackFrame) ----|
|
||||||
* - PreCoroutineStackFrameItem part of CoroutinePreflightStackFrame
|
* - Kotlin/JavaStackFrame -> PreCoroutineStackFrameItem : CoroutinePreflightStackFrame.threadPreCoroutineFrames
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class PreCoroutineStackFrameItem(val frame: StackFrameProxyImpl, location: Location, variables: List<XNamedValue> = emptyList()) :
|
class PreCoroutineStackFrameItem internal constructor(val frame: StackFrameProxyImpl, location: Location, variables: List<XNamedValue> = emptyList()) :
|
||||||
CoroutineStackFrameItem(location, variables) {
|
CoroutineStackFrameItem(location, variables) {
|
||||||
constructor(frame: StackFrameProxyImpl, variables: List<XNamedValue> = emptyList()) : this(frame, frame.location(), variables)
|
constructor(frame: StackFrameProxyImpl, variables: List<XNamedValue> = emptyList()) : this(frame, frame.location(), variables)
|
||||||
|
|
||||||
@@ -118,49 +98,34 @@ class PreCoroutineStackFrameItem(val frame: StackFrameProxyImpl, location: Locat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class RunningCoroutineStackFrameItem(
|
||||||
* Can act as a joint frame, take variables from restored frame and information from the original one.
|
val frame: StackFrameProxyImpl,
|
||||||
*/
|
location: Location,
|
||||||
class PreCoroutineStackFrame(val frame: StackFrameProxyImpl, val debugProcess: DebugProcessImpl, item: StackFrameItem) :
|
spilledVariables: List<XNamedValue> = emptyList()
|
||||||
CoroutineStackFrame(debugProcess, item) {
|
) : CoroutineStackFrameItem(location, spilledVariables) {
|
||||||
override fun computeChildren(node: XCompositeNode) {
|
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
|
||||||
debugProcess.invokeInManagerThread {
|
val realStackFrame = debugProcess.invokeInManagerThread {
|
||||||
val skipCoroutineFrame = SkipCoroutineStackFrameProxyImpl(frame)
|
debugProcess.positionManager.createStackFrame(frame, debugProcess, location)
|
||||||
debugProcess.getPositionManager().createStackFrame(skipCoroutineFrame, debugProcess, frame.location())
|
|
||||||
?.computeChildren(node) // hack but works
|
|
||||||
}
|
}
|
||||||
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<XNamedValue>) :
|
sealed class CoroutineStackFrameItem(val location: Location, val spilledVariables: List<XNamedValue>) :
|
||||||
StackFrameItem(location, spilledVariables) {
|
StackFrameItem(location, spilledVariables) {
|
||||||
val log by logger
|
val log by logger
|
||||||
|
|
||||||
|
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame =
|
||||||
|
CoroutineStackFrame(debugProcess, this)
|
||||||
|
|
||||||
fun uniqueId(): String {
|
fun uniqueId(): String {
|
||||||
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
|
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
|
||||||
location.safeLineNumber() + ":" + location.safeKotlinPreferredLineNumber()
|
location.safeLineNumber() + ":" + location.safeKotlinPreferredLineNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
|
fun isInvokeSuspend(): Boolean =
|
||||||
return CoroutineStackFrame(debugProcess, this)
|
location.isInvokeSuspend()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
class RestoredStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
||||||
StackFrameDescriptorImpl(proxy, MethodsTracker())
|
StackFrameDescriptorImpl(proxy, MethodsTracker())
|
||||||
|
|||||||
+14
-61
@@ -7,11 +7,8 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
|
|||||||
|
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.ObjectReference
|
||||||
import com.sun.jdi.ThreadReference
|
import com.sun.jdi.ThreadReference
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.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.proxy.mirror.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents state of a coroutine.
|
* Represents state of a coroutine.
|
||||||
@@ -19,10 +16,10 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
|||||||
*/
|
*/
|
||||||
data class CoroutineInfoData(
|
data class CoroutineInfoData(
|
||||||
val key: CoroutineNameIdState,
|
val key: CoroutineNameIdState,
|
||||||
val stackTrace: MutableList<CoroutineStackFrameItem>,
|
val stackTrace: List<CoroutineStackFrameItem>,
|
||||||
val creationStackTrace: List<CreationCoroutineStackFrameItem>,
|
val creationStackTrace: List<CreationCoroutineStackFrameItem>,
|
||||||
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
||||||
val lastObservedFrameFieldRef: ObjectReference? = null
|
val lastObservedFrame: ObjectReference? = null
|
||||||
) {
|
) {
|
||||||
fun isSuspended() = key.state == State.SUSPENDED
|
fun isSuspended() = key.state == State.SUSPENDED
|
||||||
|
|
||||||
@@ -32,66 +29,22 @@ data class CoroutineInfoData(
|
|||||||
|
|
||||||
fun isRunning() = key.state == State.RUNNING
|
fun isRunning() = key.state == State.RUNNING
|
||||||
|
|
||||||
|
fun topRestoredFrame() = stackTrace.firstOrNull()
|
||||||
|
|
||||||
|
fun topFrameVariables() = stackTrace.firstOrNull()?.spilledVariables ?: emptyList()
|
||||||
|
|
||||||
|
fun restoredStackTrace(mode: SuspendExitMode): List<CoroutineStackFrameItem> =
|
||||||
|
if (stackTrace.isNotEmpty() && stackTrace.first().isInvokeSuspend())
|
||||||
|
stackTrace.drop(1)
|
||||||
|
else if (mode == SuspendExitMode.SUSPEND_METHOD_PARAMETER)
|
||||||
|
stackTrace.drop(1)
|
||||||
|
else
|
||||||
|
stackTrace
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val log by logger
|
val log by logger
|
||||||
const val DEFAULT_COROUTINE_NAME = "coroutine"
|
const val DEFAULT_COROUTINE_NAME = "coroutine"
|
||||||
const val DEFAULT_COROUTINE_STATE = "UNKNOWN"
|
const val DEFAULT_COROUTINE_STATE = "UNKNOWN"
|
||||||
|
|
||||||
fun lookup(
|
|
||||||
input: ObjectReference?,
|
|
||||||
context: DefaultExecutionContext,
|
|
||||||
stackFrameItems: List<CoroutineStackFrameItem>
|
|
||||||
): CoroutineInfoData? {
|
|
||||||
val locationCache = LocationCache(context)
|
|
||||||
val creationStackTrace = mutableListOf<CreationCoroutineStackFrameItem>()
|
|
||||||
val realState = if (input?.type()?.isAbstractCoroutine() ?: false) {
|
|
||||||
state(input, context) ?: return null
|
|
||||||
} else {
|
|
||||||
val ci = DebugProbesImpl(context).getCoroutineInfo(input, context)
|
|
||||||
if (ci != null) {
|
|
||||||
if (ci.creationStackTrace != null)
|
|
||||||
for (frame in ci.creationStackTrace.mapNotNull { it.stackTraceElement() }) {
|
|
||||||
creationStackTrace.add(CreationCoroutineStackFrameItem(frame, locationCache.createLocation(frame)))
|
|
||||||
}
|
|
||||||
CoroutineNameIdState.instance(ci)
|
|
||||||
} else {
|
|
||||||
log.warn("Coroutine information not found, ${input?.type()} is not subtype of AbstractCoroutine as expected.")
|
|
||||||
CoroutineNameIdState(DEFAULT_COROUTINE_NAME, "-1", State.UNKNOWN, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CoroutineInfoData(realState, stackFrameItems.toMutableList(), creationStackTrace)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun state(value: ObjectReference?, context: DefaultExecutionContext): CoroutineNameIdState? {
|
|
||||||
value ?: return null
|
|
||||||
val reference = JavaLangMirror(context)
|
|
||||||
val standaloneCoroutine = StandaloneCoroutine(context)
|
|
||||||
val standAloneCoroutineMirror = standaloneCoroutine.mirror(value, context)
|
|
||||||
if (standAloneCoroutineMirror?.context is MirrorOfCoroutineContext) {
|
|
||||||
val id = standAloneCoroutineMirror.context.id
|
|
||||||
val name = standAloneCoroutineMirror.context.name ?: DEFAULT_COROUTINE_NAME
|
|
||||||
val toString = reference.string(value, context)
|
|
||||||
val r = """\w+\{(\w+)\}\@([\w\d]+)""".toRegex()
|
|
||||||
val matcher = r.toPattern().matcher(toString)
|
|
||||||
if (matcher.matches()) {
|
|
||||||
val state = stateOf(matcher.group(1))
|
|
||||||
val hexAddress = matcher.group(2)
|
|
||||||
return CoroutineNameIdState(name, id?.toString() ?: hexAddress, state, standAloneCoroutineMirror.context.dispatcher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stateOf(state: String?): State =
|
|
||||||
when (state) {
|
|
||||||
"Active" -> State.RUNNING
|
|
||||||
"Cancelling" -> State.SUSPENDED_CANCELLING
|
|
||||||
"Completing" -> State.SUSPENDED_COMPLETING
|
|
||||||
"Cancelled" -> State.CANCELLED
|
|
||||||
"Completed" -> State.COMPLETED
|
|
||||||
"New" -> State.NEW
|
|
||||||
else -> State.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ import javax.swing.Icon
|
|||||||
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
|
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
|
||||||
lateinit var icon: Icon
|
lateinit var icon: Icon
|
||||||
|
|
||||||
override fun getName() = infoData.key?.name
|
override fun getName() = infoData.key.name
|
||||||
|
|
||||||
@Throws(EvaluateException::class)
|
@Throws(EvaluateException::class)
|
||||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
|
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
|
||||||
|
|||||||
+116
@@ -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<StackFrameProxyImpl>,
|
||||||
|
val mode: SuspendExitMode,
|
||||||
|
val firstFrameVariables: List<XNamedValue> = 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<LocalVariableProxyImpl> {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
+93
-160
@@ -6,195 +6,128 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||||
|
|
||||||
import com.intellij.debugger.engine.JavaValue
|
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 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.data.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugMetadata
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.FieldVariable
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
|
||||||
|
|
||||||
data class ContinuationHolder(val continuation: ObjectReference, val context: DefaultExecutionContext) {
|
class ContinuationHolder(val context: DefaultExecutionContext) {
|
||||||
val log by logger
|
|
||||||
private val debugMetadata: DebugMetadata? = DebugMetadata.instance(context)
|
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 {
|
try {
|
||||||
return collectCoroutineInfo()
|
val consumer = mutableListOf<CoroutineStackFrameItem>()
|
||||||
|
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) {
|
} catch (e: Exception) {
|
||||||
log.error("Error while looking for stack frame.", e)
|
log.error("Error while looking for stack frame.", e)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectCoroutineInfo(): CoroutineInfoData? {
|
private fun findCoroutineInformation(
|
||||||
val consumer = mutableListOf<CoroutineStackFrameItem>()
|
input: ObjectReference?,
|
||||||
var completion = this
|
stackFrameItems: List<CoroutineStackFrameItem>
|
||||||
while (completion.isBaseContinuationImpl()) {
|
): CoroutineInfoData? {
|
||||||
val coroutineStackFrame = context.debugProcess.invokeInManagerThread {
|
val creationStackTrace = mutableListOf<CreationCoroutineStackFrameItem>()
|
||||||
completion.createLocation()
|
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? {
|
fun state(value: ObjectReference?): CoroutineNameIdState? {
|
||||||
val stackTraceElementMirror = debugMetadata?.getStackTraceElement(continuation, context) ?: return null
|
value ?: return null
|
||||||
val stackTraceElement = stackTraceElementMirror.stackTraceElement()
|
val reference = JavaLangMirror(context)
|
||||||
val locationClass = context.findClassSafe(stackTraceElement.className) ?: return null
|
val standaloneCoroutine = StandaloneCoroutine(context)
|
||||||
val generatedLocation =
|
val standAloneCoroutineMirror = standaloneCoroutine.mirror(value, context)
|
||||||
GeneratedLocation(context.debugProcess, locationClass, stackTraceElement.methodName, stackTraceElement.lineNumber)
|
if (standAloneCoroutineMirror?.context is MirrorOfCoroutineContext) {
|
||||||
val spilledVariables = getSpilledVariables() ?: emptyList()
|
val id = standAloneCoroutineMirror.context.id
|
||||||
return DefaultCoroutineStackFrameItem(generatedLocation, spilledVariables)
|
val name = standAloneCoroutineMirror.context.name ?: CoroutineInfoData.DEFAULT_COROUTINE_NAME
|
||||||
}
|
val toString = reference.string(value, context)
|
||||||
|
val r = """\w+\{(\w+)\}\@([\w\d]+)""".toRegex()
|
||||||
fun getSpilledVariables(): List<XNamedValue>? {
|
val matcher = r.toPattern().matcher(toString)
|
||||||
val variables: List<JavaValue> = context.debugProcess.invokeInManagerThread {
|
if (matcher.matches()) {
|
||||||
debugMetadata?.getSpilledVariableFieldMapping(continuation, context)?.mapNotNull {
|
val state = stateOf(matcher.group(1))
|
||||||
fieldVariableToNamedValue(it, this)
|
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
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isBaseContinuationImpl() =
|
private fun createStackFrameItem(
|
||||||
continuation.type().isBaseContinuationImpl()
|
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 {
|
companion object {
|
||||||
val log by logger
|
val log by logger
|
||||||
|
|
||||||
fun coroutineExitFrame(
|
private fun stateOf(state: String?): State =
|
||||||
frame: StackFrameProxyImpl,
|
when (state) {
|
||||||
suspendContext: SuspendContextImpl?
|
"Active" -> State.RUNNING
|
||||||
): XStackFrame? {
|
"Cancelling" -> State.SUSPENDED_CANCELLING
|
||||||
suspendContext ?: return null
|
"Completing" -> State.SUSPENDED_COMPLETING
|
||||||
return suspendContext.invokeInManagerThread {
|
"Cancelled" -> State.CANCELLED
|
||||||
if (frame.location().isPreFlight() || frame.location().isPreExitFrame()) {
|
"Completed" -> State.COMPLETED
|
||||||
if (coroutineDebuggerTraceEnabled())
|
"New" -> State.NEW
|
||||||
log.trace("Entry frame found: ${formatLocation(frame.location())}")
|
else -> State.UNKNOWN
|
||||||
val leftThreadStack = leftThreadStack(frame) ?: return@invokeInManagerThread null
|
|
||||||
lookupContinuation(suspendContext, frame, leftThreadStack)
|
|
||||||
} else
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun leftThreadStack(frame: StackFrameProxyImpl): List<StackFrameProxyImpl>? {
|
|
||||||
var frames = frame.threadProxy().frames()
|
|
||||||
val indexOfCurrentFrame = frames.indexOf(frame)
|
|
||||||
if (indexOfCurrentFrame >= 0) {
|
|
||||||
val indexofGetCoroutineSuspended = hasGetCoroutineSuspended(frames)
|
|
||||||
// @TODO if found - skip this thread stack
|
|
||||||
if (indexofGetCoroutineSuspended >= 0)
|
|
||||||
return null
|
|
||||||
return frames.drop(indexOfCurrentFrame)
|
|
||||||
} else
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lookupContinuation(
|
|
||||||
suspendContext: SuspendContextImpl,
|
|
||||||
previousFrame: StackFrameProxyImpl, // invokeSuspend
|
|
||||||
framesLeft: List<StackFrameProxyImpl>
|
|
||||||
): CoroutinePreflightStackFrame? {
|
|
||||||
val continuation =
|
|
||||||
if (previousFrame.safeLocation()?.method()?.isSuspendLambda() ?: false ||
|
|
||||||
previousFrame.safeLocation()?.method()?.isContinuation() ?: false
|
|
||||||
)
|
|
||||||
getThisContinuation(previousFrame)
|
|
||||||
else
|
|
||||||
null
|
|
||||||
|
|
||||||
if (continuation != null) {
|
|
||||||
val context = suspendContext.executionContext() ?: return null
|
|
||||||
val coroutineStackTrace = ContinuationHolder(continuation, context).getCoroutineInfoData() ?: return null
|
|
||||||
return CoroutinePreflightStackFrame.preflight(
|
|
||||||
previousFrame,
|
|
||||||
coroutineStackTrace,
|
|
||||||
framesLeft
|
|
||||||
)
|
|
||||||
} else
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCompletionContinuation(previousFrame: StackFrameProxyImpl?) =
|
|
||||||
previousFrame?.completion1VariableValue()
|
|
||||||
|
|
||||||
private fun getThisContinuation(previousFrame: StackFrameProxyImpl?): ObjectReference? =
|
|
||||||
previousFrame?.thisVariableValue()
|
|
||||||
|
|
||||||
|
|
||||||
private fun formatLocation(location: Location): String {
|
|
||||||
return "${location.method().name()}:${location.lineNumber()}, ${location.method().declaringType()} in ${location.sourceName()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<JavaValue> {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
+2
-4
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
|||||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.openapi.util.registry.Registry
|
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.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.coroutine.util.logger
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
@@ -44,8 +44,6 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
|||||||
return CoroutineNoLibraryProxy(executionContext)
|
return CoroutineNoLibraryProxy(executionContext)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun frameBuilder() = CoroutineBuilder(suspendContext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun standaloneCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines.standalone")
|
fun standaloneCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines.standalone")
|
||||||
|
|||||||
+21
-16
@@ -5,17 +5,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||||
|
|
||||||
import com.intellij.debugger.engine.DebugProcess
|
import com.intellij.xdebugger.frame.XNamedValue
|
||||||
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.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.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.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.coroutine.util.logger
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
@@ -24,6 +19,7 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution
|
|||||||
val log by logger
|
val log by logger
|
||||||
private val debugProbesImpl = DebugProbesImpl(executionContext)
|
private val debugProbesImpl = DebugProbesImpl(executionContext)
|
||||||
private val locationCache = LocationCache(executionContext)
|
private val locationCache = LocationCache(executionContext)
|
||||||
|
private val debugMetadata: DebugMetadata? = DebugMetadata.instance(executionContext)
|
||||||
|
|
||||||
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
||||||
val result = debugProbesImpl.dumpCoroutinesInfo(executionContext)
|
val result = debugProbesImpl.dumpCoroutinesInfo(executionContext)
|
||||||
@@ -33,10 +29,14 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution
|
|||||||
fun mapToCoroutineInfoData(mirror: MirrorOfCoroutineInfo): CoroutineInfoData? {
|
fun mapToCoroutineInfoData(mirror: MirrorOfCoroutineInfo): CoroutineInfoData? {
|
||||||
val cnis = CoroutineNameIdState.instance(mirror)
|
val cnis = CoroutineNameIdState.instance(mirror)
|
||||||
val stackTrace = mirror.enchancedStackTrace?.mapNotNull { it.stackTraceElement() } ?: emptyList()
|
val stackTrace = mirror.enchancedStackTrace?.mapNotNull { it.stackTraceElement() } ?: emptyList()
|
||||||
var stackFrames = findStackFrames(stackTrace)
|
val variables: List<XNamedValue> = mirror.lastObservedFrame?.let {
|
||||||
|
val spilledVariables = debugMetadata?.baseContinuationImpl?.mirror(it, executionContext)
|
||||||
|
spilledVariables?.spilledValues(executionContext)
|
||||||
|
} ?: emptyList()
|
||||||
|
var stackFrames = findStackFrames(stackTrace, variables)
|
||||||
return CoroutineInfoData(
|
return CoroutineInfoData(
|
||||||
cnis,
|
cnis,
|
||||||
stackFrames.restoredStackFrames.toMutableList(),
|
stackFrames.restoredStackFrames,
|
||||||
stackFrames.creationStackFrames,
|
stackFrames.creationStackFrames,
|
||||||
mirror.lastObservedThread,
|
mirror.lastObservedThread,
|
||||||
mirror.lastObservedFrame
|
mirror.lastObservedFrame
|
||||||
@@ -52,13 +52,18 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findStackFrames(frames: List<StackTraceElement>): CoroutineStackFrames {
|
private fun findStackFrames(
|
||||||
|
frames: List<StackTraceElement>,
|
||||||
|
variables: List<XNamedValue>
|
||||||
|
): CoroutineStackFrames {
|
||||||
val index = frames.indexOfFirst { it.isCreationSeparatorFrame() }
|
val index = frames.indexOfFirst { it.isCreationSeparatorFrame() }
|
||||||
return CoroutineStackFrames(frames.take(index).map {
|
val restoredStackFrames = frames.take(index).map {
|
||||||
SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it))
|
SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it), variables)
|
||||||
}, frames.subList(index + 1, frames.size).map {
|
}
|
||||||
CreationCoroutineStackFrameItem(it, locationCache.createLocation(it))
|
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(
|
data class CoroutineStackFrames(
|
||||||
|
|||||||
+5
-37
@@ -5,17 +5,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
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.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.sun.jdi.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineContext
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineContext
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineInfo
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isCreationSeparatorFrame
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, private val executionContext: DefaultExecutionContext) :
|
class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, private val executionContext: DefaultExecutionContext) :
|
||||||
@@ -43,8 +38,7 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva
|
|||||||
|
|
||||||
// value
|
// value
|
||||||
private val vm = executionContext.vm
|
private val vm = executionContext.vm
|
||||||
private val classesByName = ClassesByNameProvider.createCache(vm.allClasses())
|
private val locationCache = LocationCache(executionContext)
|
||||||
|
|
||||||
|
|
||||||
private val coroutineContext: CoroutineContext = CoroutineContext(executionContext)
|
private val coroutineContext: CoroutineContext = CoroutineContext(executionContext)
|
||||||
|
|
||||||
@@ -79,11 +73,11 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva
|
|||||||
val coroutineStackTrace = stackTrace.take(creationFrameSeparatorIndex)
|
val coroutineStackTrace = stackTrace.take(creationFrameSeparatorIndex)
|
||||||
|
|
||||||
val coroutineStackTraceFrameItems = coroutineStackTrace.map {
|
val coroutineStackTraceFrameItems = coroutineStackTrace.map {
|
||||||
SuspendCoroutineStackFrameItem(it, createLocation(it))
|
SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it))
|
||||||
}
|
}
|
||||||
val creationStackTrace = stackTrace.subList(creationFrameSeparatorIndex + 1, stackTrace.size)
|
val creationStackTrace = stackTrace.subList(creationFrameSeparatorIndex + 1, stackTrace.size)
|
||||||
val creationStackTraceFrameItems = creationStackTrace.map {
|
val creationStackTraceFrameItems = creationStackTrace.mapIndexed { index, stackTraceElement ->
|
||||||
CreationCoroutineStackFrameItem(it, createLocation(it))
|
CreationCoroutineStackFrameItem(stackTraceElement, locationCache.createLocation(stackTraceElement), index == 0)
|
||||||
}
|
}
|
||||||
val key = CoroutineNameIdState(name, "", State.valueOf(state), "")
|
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
|
* Tries to find creation frame separator if any, returns last index if none found
|
||||||
*/
|
*/
|
||||||
|
|||||||
+7
-3
@@ -9,12 +9,16 @@ import com.intellij.openapi.util.registry.Registry
|
|||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
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.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.coroutine.util.logger
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : CoroutineInfoProvider {
|
class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : CoroutineInfoProvider {
|
||||||
val log by logger
|
val log by logger
|
||||||
val debugMetadataKtType = executionContext.findCoroutineMetadataType()
|
val debugMetadataKtType = executionContext.findCoroutineMetadataType()
|
||||||
|
val holder = ContinuationHolder(executionContext)
|
||||||
|
|
||||||
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
||||||
val vm = executionContext.vm
|
val vm = executionContext.vm
|
||||||
@@ -51,7 +55,7 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
|
|||||||
): CoroutineInfoData? {
|
): CoroutineInfoData? {
|
||||||
val mirror = ccMirrorProvider.mirror(dispatchedContinuation, executionContext) ?: return null
|
val mirror = ccMirrorProvider.mirror(dispatchedContinuation, executionContext) ?: return null
|
||||||
val continuation = mirror.delegate?.continuation ?: return null
|
val continuation = mirror.delegate?.continuation ?: return null
|
||||||
return ContinuationHolder(continuation, executionContext).getCoroutineInfoData()
|
return holder.extractCoroutineInfoData(continuation)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dispatchedContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
|
private fun dispatchedContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
|
||||||
@@ -68,10 +72,10 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? {
|
private fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? {
|
||||||
debugMetadataKtType ?: return null
|
debugMetadataKtType ?: return null
|
||||||
val initialContinuation = dispatchedContinuation.getValue(continuation) as ObjectReference
|
val initialContinuation = dispatchedContinuation.getValue(continuation) as ObjectReference
|
||||||
return ContinuationHolder(initialContinuation, executionContext).getCoroutineInfoData()
|
return holder.extractCoroutineInfoData(initialContinuation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-101
@@ -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<StackFrameProxyImpl>,
|
|
||||||
) : 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<StackFrameProxyImpl>
|
|
||||||
): 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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+2
-2
@@ -17,13 +17,13 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
|||||||
class LocationCache(val context: DefaultExecutionContext) {
|
class LocationCache(val context: DefaultExecutionContext) {
|
||||||
private val classesByName = ClassesByNameProvider.createCache(context.vm.allClasses())
|
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]),
|
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
|
||||||
stackTraceElement.methodName,
|
stackTraceElement.methodName,
|
||||||
stackTraceElement.lineNumber
|
stackTraceElement.lineNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun findLocation(
|
fun createLocation(
|
||||||
type: ReferenceType?,
|
type: ReferenceType?,
|
||||||
methodName: String,
|
methodName: String,
|
||||||
line: Int
|
line: Int
|
||||||
|
|||||||
-1
@@ -17,6 +17,5 @@ class LocationStackFrameProxyImpl(val location: Location, frame: StackFrameProxy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SkipCoroutineStackFrameProxyImpl(frame: StackFrameProxyImpl) :
|
class SkipCoroutineStackFrameProxyImpl(frame: StackFrameProxyImpl) :
|
||||||
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom)
|
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom)
|
||||||
|
|||||||
+39
-8
@@ -6,13 +6,15 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
|
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
|
||||||
|
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
class DebugProbesImpl(context: DefaultExecutionContext) :
|
class DebugProbesImpl internal constructor(context: DefaultExecutionContext) :
|
||||||
BaseMirror<MirrorOfDebugProbesImpl>("kotlinx.coroutines.debug.internal.DebugProbesImpl", context) {
|
BaseMirror<MirrorOfDebugProbesImpl>("kotlinx.coroutines.debug.internal.DebugProbesImpl", context) {
|
||||||
val javaLangListMirror = JavaUtilList(context)
|
val javaLangListMirror = JavaUtilAbstractCollection(context)
|
||||||
val stackTraceElement = StackTraceElement(context)
|
val stackTraceElement = StackTraceElement(context)
|
||||||
val coroutineInfo = CoroutineInfo(this, context)
|
val coroutineInfo = CoroutineInfo(this, context)
|
||||||
|
val debugProbesCoroutineOwner = DebugProbesImpl_CoroutineOwner(coroutineInfo, context)
|
||||||
val instance = staticObjectValue("INSTANCE")
|
val instance = staticObjectValue("INSTANCE")
|
||||||
val isInstalledMethod = makeMethod("isInstalled\$kotlinx_coroutines_debug", "()Z")
|
val isInstalledMethod = makeMethod("isInstalled\$kotlinx_coroutines_debug", "()Z")
|
||||||
val isInstalledValue = booleanValue(instance, isInstalledMethod, context)
|
val isInstalledValue = booleanValue(instance, isInstalledMethod, context)
|
||||||
@@ -41,20 +43,46 @@ class DebugProbesImpl(context: DefaultExecutionContext) :
|
|||||||
return referenceList.values.mapNotNull { coroutineInfo.mirror(it, context) }
|
return referenceList.values.mapNotNull { coroutineInfo.mirror(it, context) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCoroutineInfo(input: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? {
|
fun getCoroutineInfo(value: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? {
|
||||||
// kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner
|
val coroutineOwner = debugProbesCoroutineOwner.mirror(value, context)
|
||||||
val delegate = input?.referenceType()?.fieldByName("info") ?: return null
|
return coroutineOwner?.coroutineInfo
|
||||||
val coroutine = input.getValue(delegate) as? ObjectReference
|
}
|
||||||
return coroutineInfo.mirror(coroutine, context)
|
|
||||||
|
companion object {
|
||||||
|
fun instance(context: DefaultExecutionContext) =
|
||||||
|
try {
|
||||||
|
DebugProbesImpl(context)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DebugProbesImpl_CoroutineOwner(val coroutineInfo: CoroutineInfo, context: DefaultExecutionContext) :
|
||||||
|
BaseMirror<MirrorOfCoroutineOwner>(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?)
|
data class MirrorOfDebugProbesImpl(val that: ObjectReference, val instance: ObjectReference?, val isInstalled: Boolean?)
|
||||||
|
|
||||||
class CoroutineInfo(val debugProbesImplMirror: DebugProbesImpl, context: DefaultExecutionContext) :
|
class CoroutineInfo(val debugProbesImplMirror: DebugProbesImpl, context: DefaultExecutionContext) :
|
||||||
BaseMirror<MirrorOfCoroutineInfo>("kotlinx.coroutines.debug.CoroutineInfo", context) {
|
BaseMirror<MirrorOfCoroutineInfo>("kotlinx.coroutines.debug.CoroutineInfo", context) {
|
||||||
val javaLangMirror = JavaLangMirror(context)
|
val javaLangMirror = JavaLangMirror(context)
|
||||||
val javaLangListMirror = JavaUtilList(context)
|
val javaLangListMirror = JavaUtilAbstractCollection(context)
|
||||||
private val coroutineContextMirror = CoroutineContext(context)
|
private val coroutineContextMirror = CoroutineContext(context)
|
||||||
private val coroutineStackFrameMirror = CoroutineStackFrame(context)
|
private val coroutineStackFrameMirror = CoroutineStackFrame(context)
|
||||||
private val stackTraceElement = StackTraceElement(context)
|
private val stackTraceElement = StackTraceElement(context)
|
||||||
@@ -182,6 +210,9 @@ data class MirrorOfStackTraceElement(
|
|||||||
val lineNumber: Int?,
|
val lineNumber: Int?,
|
||||||
val format: Byte?
|
val format: Byte?
|
||||||
) {
|
) {
|
||||||
|
fun isInvokeSuspend() =
|
||||||
|
"invokeSuspend" == methodName
|
||||||
|
|
||||||
fun stackTraceElement() =
|
fun stackTraceElement() =
|
||||||
java.lang.StackTraceElement(
|
java.lang.StackTraceElement(
|
||||||
declaringClass,
|
declaringClass,
|
||||||
|
|||||||
+15
-7
@@ -6,7 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
|
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
|
||||||
|
|
||||||
import com.sun.jdi.*
|
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.coroutine.util.logger
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ abstract class BaseMirror<T>(val name: String, context: DefaultExecutionContext)
|
|||||||
fun makeMethod(methodName: String, signature: String): Method? =
|
fun makeMethod(methodName: String, signature: String): Method? =
|
||||||
cls?.let { it.methodsByName(methodName, signature).single() }
|
cls?.let { it.methodsByName(methodName, signature).single() }
|
||||||
|
|
||||||
fun isCompatible(value: ObjectReference) =
|
fun isCompatible(value: ObjectReference?) =
|
||||||
value.referenceType().isSubTypeOrSame(name)
|
value?.let { it.referenceType().isSubTypeOrSame(name) } ?: false
|
||||||
|
|
||||||
fun mirror(value: ObjectReference?, context: DefaultExecutionContext): T? {
|
fun mirror(value: ObjectReference?, context: DefaultExecutionContext): T? {
|
||||||
value ?: return null
|
value ?: return null
|
||||||
@@ -56,16 +56,24 @@ abstract class BaseMirror<T>(val name: String, context: DefaultExecutionContext)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stringValue(value: ObjectReference, field: Field?) =
|
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?) =
|
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?) =
|
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) =
|
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) =
|
fun objectValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
|
||||||
value?.let {
|
value?.let {
|
||||||
|
|||||||
+67
-23
@@ -9,29 +9,76 @@ import com.sun.jdi.ArrayReference
|
|||||||
import com.sun.jdi.ClassType
|
import com.sun.jdi.ClassType
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.ObjectReference
|
||||||
import com.sun.jdi.StringReference
|
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
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
class DebugMetadata(context: DefaultExecutionContext) :
|
class DebugMetadata internal constructor(context: DefaultExecutionContext) :
|
||||||
BaseMirror<MirrorOfDebugProbesImpl>("kotlin.coroutines.jvm.internal.DebugMetadataKt", context) {
|
BaseMirror<MirrorOfDebugProbesImpl>("kotlin.coroutines.jvm.internal.DebugMetadataKt", context) {
|
||||||
val getStackTraceElementMethod = makeMethod("getStackTraceElement")
|
private val getStackTraceElementMethod = makeMethod("getStackTraceElement")
|
||||||
val getSpilledVariableFieldMappingMethod = makeMethod("getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;")
|
private val getSpilledVariableFieldMappingMethod =
|
||||||
val stackTraceElement = StackTraceElement(context)
|
makeMethod("getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;")
|
||||||
|
val baseContinuationImpl = BaseContinuationImpl(context, this);
|
||||||
|
|
||||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? {
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? {
|
||||||
|
// @TODO fix this
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStackTraceElement(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStackTraceElement? {
|
fun fetchContinuationStack(continuation: ObjectReference, context: DefaultExecutionContext): MirrorOfContinuationStack {
|
||||||
if (value.referenceType().isBaseContinuationImpl()) {
|
val coroutineStack = mutableListOf<MirrorOfStackFrame>()
|
||||||
val stackTraceObjectReference = staticMethodValue(getStackTraceElementMethod, context, value) ?: return null
|
var loopContinuation: ObjectReference? = continuation
|
||||||
return stackTraceElement.mirror(stackTraceObjectReference, context)
|
while (loopContinuation != null) {
|
||||||
} else
|
val continuationMirror = baseContinuationImpl.mirror(loopContinuation, context) ?: break
|
||||||
return null
|
coroutineStack.add(MirrorOfStackFrame(loopContinuation, continuationMirror))
|
||||||
|
loopContinuation = continuationMirror.nextContinuation
|
||||||
|
}
|
||||||
|
return MirrorOfContinuationStack(continuation, coroutineStack)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext): List<FieldVariable> {
|
fun getStackTraceElement(value: ObjectReference, context: DefaultExecutionContext) =
|
||||||
val getSpilledVariableFieldMappingReference = staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference ?: return emptyList()
|
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<MirrorOfStackFrame>)
|
||||||
|
|
||||||
|
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<MirrorOfBaseContinuationImpl>("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<FieldVariable> {
|
||||||
|
val getSpilledVariableFieldMappingReference =
|
||||||
|
debugMetadata.getSpilledVariableFieldMapping(value, context) ?: return emptyList()
|
||||||
|
|
||||||
val length = getSpilledVariableFieldMappingReference.length() / 2
|
val length = getSpilledVariableFieldMappingReference.length() / 2
|
||||||
val fieldVariables = ArrayList<FieldVariable>()
|
val fieldVariables = ArrayList<FieldVariable>()
|
||||||
@@ -46,15 +93,12 @@ class DebugMetadata(context: DefaultExecutionContext) :
|
|||||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: return null
|
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: return null
|
||||||
return FieldVariable(fieldName, variableName)
|
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)
|
data class MirrorOfBaseContinuationImpl(
|
||||||
|
val that: ObjectReference,
|
||||||
|
val stackTraceElement: MirrorOfStackTraceElement?,
|
||||||
|
val fieldVariables: List<FieldVariable>,
|
||||||
|
val nextContinuation: ObjectReference?,
|
||||||
|
val coroutineOwner: ObjectReference?
|
||||||
|
)
|
||||||
+20
-8
@@ -61,21 +61,33 @@ class JavaLangMirror(context: DefaultExecutionContext) {
|
|||||||
(instance.getValue(declaringClassFieldRef) as? StringReference)?.value() ?: ""
|
(instance.getValue(declaringClassFieldRef) as? StringReference)?.value() ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
class JavaUtilList(context: DefaultExecutionContext) :
|
class JavaUtilAbstractCollection(context: DefaultExecutionContext) :
|
||||||
BaseMirror<MirrorOfJavaLangList>("java.util.List", context) {
|
BaseMirror<MirrorOfJavaLangAbstractCollection>("java.util.AbstractCollection", context) {
|
||||||
|
val abstractList = JavaUtilAbstractList(context)
|
||||||
val sizeMethod = makeMethod("size")
|
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<ObjectReference>()
|
val list = mutableListOf<ObjectReference>()
|
||||||
val size = intValue(value, sizeMethod, context) ?: 0
|
val size = intValue(value, sizeMethod, context) ?: 0
|
||||||
for (it in 0 until size) {
|
for (index in 0 until size) {
|
||||||
val reference = objectValue(value, getMethod, context, context.vm.mirrorOf(it)) ?: continue
|
val reference = abstractList.get(value, index, context) ?: continue
|
||||||
list.add(reference)
|
list.add(reference)
|
||||||
}
|
}
|
||||||
return MirrorOfJavaLangList(value, list)
|
return MirrorOfJavaLangAbstractCollection(value, list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MirrorOfJavaLangList(val that: ObjectReference, val values: List<ObjectReference>)
|
class JavaUtilAbstractList(context: DefaultExecutionContext) :
|
||||||
|
BaseMirror<ObjectReference>("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<ObjectReference>)
|
||||||
|
|
||||||
|
|||||||
+177
@@ -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<CoroutineStackFrameItem>()
|
||||||
|
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<CoroutineStackFrameItem>()
|
||||||
|
|
||||||
|
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<CoroutineStackFrameItem>,
|
||||||
|
val creationStackTrace: List<CreationCoroutineStackFrameItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
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<StackFrameProxyImpl>,
|
||||||
|
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<StackFrameProxyImpl>? {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-21
@@ -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.
|
* 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.SuspendContextImpl
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
@@ -17,10 +17,12 @@ import com.intellij.xdebugger.XDebuggerUtil
|
|||||||
import com.intellij.xdebugger.XSourcePosition
|
import com.intellij.xdebugger.XSourcePosition
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.*
|
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.debugger.evaluate.DefaultExecutionContext
|
||||||
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
|
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 =
|
fun Method.isInvokeSuspend(): Boolean =
|
||||||
name() == "invokeSuspend" && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;"
|
name() == "invokeSuspend" && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;"
|
||||||
|
|
||||||
@@ -30,12 +32,21 @@ fun Method.isContinuation() =
|
|||||||
fun Method.isSuspendLambda() =
|
fun Method.isSuspendLambda() =
|
||||||
isInvokeSuspend() && declaringType().isSuspendLambda()
|
isInvokeSuspend() && declaringType().isSuspendLambda()
|
||||||
|
|
||||||
|
fun Method.hasContinuationParameter() =
|
||||||
|
signature().contains("Lkotlin/coroutines/Continuation;)")
|
||||||
|
|
||||||
fun Method.isResumeWith() =
|
fun Method.isResumeWith() =
|
||||||
name() == "resumeWith" && signature() == "(Ljava/lang/Object;)V" && (declaringType().isSuspendLambda() || declaringType().isContinuation())
|
name() == "resumeWith" && signature() == "(Ljava/lang/Object;)V" && (declaringType().isSuspendLambda() || declaringType().isContinuation())
|
||||||
|
|
||||||
fun Location.isPreFlight(): Boolean {
|
fun Location.isPreFlight(): SuspendExitMode {
|
||||||
val method = safeMethod() ?: return false
|
val method = safeMethod() ?: return SuspendExitMode.NONE
|
||||||
return method.isSuspendLambda() || method.isContinuation()
|
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() =
|
fun ReferenceType.isContinuation() =
|
||||||
@@ -53,8 +64,8 @@ fun Type.isSubTypeOrSame(className: String) =
|
|||||||
fun ReferenceType.isSuspendLambda() =
|
fun ReferenceType.isSuspendLambda() =
|
||||||
SUSPEND_LAMBDA_CLASSES.any { isSubtype(it) }
|
SUSPEND_LAMBDA_CLASSES.any { isSubtype(it) }
|
||||||
|
|
||||||
fun Location.isPreExitFrame() =
|
fun Location.isInvokeSuspend() =
|
||||||
safeMethod()?.isResumeWith() ?: false
|
safeMethod()?.isInvokeSuspend() ?: false
|
||||||
|
|
||||||
fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
|
fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
|
||||||
val continuationVariable = safeVisibleVariableByName(variableName) ?: return null
|
val continuationVariable = safeVisibleVariableByName(variableName) ?: return null
|
||||||
@@ -64,8 +75,8 @@ fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
|
|||||||
fun StackFrameProxyImpl.completionVariableValue(): ObjectReference? =
|
fun StackFrameProxyImpl.completionVariableValue(): ObjectReference? =
|
||||||
variableValue("completion")
|
variableValue("completion")
|
||||||
|
|
||||||
fun StackFrameProxyImpl.completion1VariableValue(): ObjectReference? =
|
fun StackFrameProxyImpl.continuationVariableValue(): ObjectReference? =
|
||||||
variableValue("completion")
|
variableValue("\$continuation")
|
||||||
|
|
||||||
fun StackFrameProxyImpl.thisVariableValue(): ObjectReference? =
|
fun StackFrameProxyImpl.thisVariableValue(): ObjectReference? =
|
||||||
this.thisObject()
|
this.thisObject()
|
||||||
@@ -86,7 +97,7 @@ fun hasGetCoroutineSuspended(frames: List<StackFrameProxyImpl>) =
|
|||||||
frames.indexOfFirst { it.safeLocation()?.safeMethod()?.isGetCOROUTINE_SUSPENDED() == true }
|
frames.indexOfFirst { it.safeLocation()?.safeMethod()?.isGetCOROUTINE_SUSPENDED() == true }
|
||||||
|
|
||||||
fun StackTraceElement.isCreationSeparatorFrame() =
|
fun StackTraceElement.isCreationSeparatorFrame() =
|
||||||
className.startsWith(CoroutineBuilder.CREATION_STACK_TRACE_SEPARATOR)
|
className.startsWith(CREATION_STACK_TRACE_SEPARATOR)
|
||||||
|
|
||||||
fun StackTraceElement.findPosition(project: Project): XSourcePosition? =
|
fun StackTraceElement.findPosition(project: Project): XSourcePosition? =
|
||||||
getPosition(project, className, lineNumber)
|
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
|
val localLineNumber = if (lineNumber > 0) lineNumber - 1 else return null
|
||||||
return XDebuggerUtil.getInstance().createPosition(classFile, localLineNumber)
|
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() =
|
fun SuspendContextImpl.executionContext() =
|
||||||
invokeInManagerThread { DefaultExecutionContext(EvaluationContextImpl(this, this.frameProxy)) }
|
invokeInManagerThread { DefaultExecutionContext(EvaluationContextImpl(this, this.frameProxy)) }
|
||||||
@@ -134,3 +134,8 @@ fun SuspendContextImpl.supportsEvaluation() =
|
|||||||
|
|
||||||
fun XDebugSession.suspendContextImpl() =
|
fun XDebugSession.suspendContextImpl() =
|
||||||
suspendContext as SuspendContextImpl
|
suspendContext as SuspendContextImpl
|
||||||
|
|
||||||
|
fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl?) =
|
||||||
|
suspendContext.invokeInManagerThread {
|
||||||
|
suspendContext.supportsEvaluation() && frameProxy?.threadProxy()?.supportsEvaluation() ?: false
|
||||||
|
} ?: false
|
||||||
+33
-3
@@ -5,14 +5,24 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.util
|
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.project.Project
|
||||||
import com.intellij.openapi.ui.MessageType
|
import com.intellij.openapi.ui.MessageType
|
||||||
|
import com.intellij.openapi.util.registry.Registry
|
||||||
import com.intellij.psi.JavaPsiFacade
|
import com.intellij.psi.JavaPsiFacade
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.xdebugger.XDebuggerUtil
|
import com.intellij.xdebugger.XDebuggerUtil
|
||||||
import com.intellij.xdebugger.XSourcePosition
|
import com.intellij.xdebugger.XSourcePosition
|
||||||
|
import com.intellij.xdebugger.frame.XStackFrame
|
||||||
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
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.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? {
|
fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSourcePosition? {
|
||||||
val psiFacade = JavaPsiFacade.getInstance(project)
|
val psiFacade = JavaPsiFacade.getInstance(project)
|
||||||
@@ -31,7 +41,27 @@ fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSource
|
|||||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProjectNotification(val project: Project) {
|
fun Location.format(): String {
|
||||||
fun error(message: String) =
|
val method = safeMethod()
|
||||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project)
|
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()
|
||||||
|
|||||||
+6
-1
@@ -5,9 +5,13 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.util
|
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.openapi.diagnostic.Logger
|
||||||
import com.intellij.xdebugger.XDebugSession
|
import com.intellij.xdebugger.XDebugSession
|
||||||
import com.intellij.xdebugger.XDebugSessionListener
|
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.Icon
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
@@ -33,8 +37,9 @@ class LoggerDelegate : ReadOnlyProperty<Any, Logger> {
|
|||||||
lateinit var logger: Logger
|
lateinit var logger: Logger
|
||||||
|
|
||||||
override fun getValue(thisRef: Any, property: KProperty<*>): Logger {
|
override fun getValue(thisRef: Any, property: KProperty<*>): Logger {
|
||||||
if (!::logger.isInitialized)
|
if (!::logger.isInitialized) {
|
||||||
logger = Logger.getInstance(thisRef.javaClass)
|
logger = Logger.getInstance(thisRef.javaClass)
|
||||||
|
}
|
||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -181,7 +181,7 @@ class SimpleColoredTextIconPresentationRenderer {
|
|||||||
SimpleColoredTextIcon(
|
SimpleColoredTextIcon(
|
||||||
AllIcons.Debugger.ThreadSuspended,
|
AllIcons.Debugger.ThreadSuspended,
|
||||||
true,
|
true,
|
||||||
KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.frame", infoData.key.name)
|
KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.trace")
|
||||||
)
|
)
|
||||||
|
|
||||||
fun renderErrorNode(error: String) =
|
fun renderErrorNode(error: String) =
|
||||||
|
|||||||
+7
-14
@@ -5,11 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.view
|
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.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.jdi.ClassesByNameProvider
|
|
||||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
|
||||||
import com.intellij.ide.CommonActionsManager
|
import com.intellij.ide.CommonActionsManager
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.actionSystem.ActionManager
|
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.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider
|
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.CoroutineInfoData
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CreationCoroutineStackFrameItem
|
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) {
|
override fun computeChildren(node: XCompositeNode) {
|
||||||
managerThreadExecutor.on(suspendContext).schedule {
|
managerThreadExecutor.on(suspendContext).schedule {
|
||||||
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
|
|
||||||
val children = XValueChildrenList()
|
val children = XValueChildrenList()
|
||||||
var stackFrames = debugProbesProxy.frameBuilder().build(infoData)
|
val doubleFrameList = CoroutineFrameBuilder.build(infoData, suspendContext)
|
||||||
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
|
doubleFrameList?.stackTrace?.forEach {
|
||||||
for (frame in stackFrames) {
|
children.add(CoroutineFrameValue(infoData, it))
|
||||||
if (frame is CreationCoroutineStackFrameItem)
|
}
|
||||||
creationStack.add(frame)
|
doubleFrameList?.creationStackTrace?.let {
|
||||||
else
|
children.add(CreationFramesContainer(infoData, it))
|
||||||
children.add(CoroutineFrameValue(infoData, frame))
|
|
||||||
}
|
}
|
||||||
if (creationStack.isNotEmpty())
|
|
||||||
children.add(CreationFramesContainer(infoData, creationStack))
|
|
||||||
node.addChildren(children, true)
|
node.addChildren(children, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-38
@@ -8,8 +8,6 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.view
|
|||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.engine.JavaDebugProcess
|
import com.intellij.debugger.engine.JavaDebugProcess
|
||||||
import com.intellij.debugger.engine.JavaExecutionStack
|
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.debugger.jdi.ThreadReferenceProxyImpl
|
||||||
import com.intellij.ui.DoubleClickListener
|
import com.intellij.ui.DoubleClickListener
|
||||||
import com.intellij.xdebugger.XDebugSession
|
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.frame.XStackFrame
|
||||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
|
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
|
||||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
|
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.data.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor
|
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.util.findPosition
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.findPosition
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.invokeInManagerThread
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.suspendContextImpl
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.suspendContextImpl
|
||||||
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
||||||
import java.awt.event.KeyAdapter
|
import java.awt.event.KeyAdapter
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
@@ -61,11 +58,10 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
|
|||||||
is RunningCoroutineStackFrameItem -> {
|
is RunningCoroutineStackFrameItem -> {
|
||||||
val threadProxy = stackFrameItem.frame.threadProxy()
|
val threadProxy = stackFrameItem.frame.threadProxy()
|
||||||
val isCurrentContext = suspendContext.thread == threadProxy
|
val isCurrentContext = suspendContext.thread == threadProxy
|
||||||
val executionStack = JavaExecutionStack(
|
val executionStack = suspendContext.invokeInManagerThread { JavaExecutionStack(
|
||||||
threadProxy,
|
threadProxy,
|
||||||
debugProcess,
|
debugProcess,
|
||||||
isCurrentContext
|
isCurrentContext) } ?: return false
|
||||||
)
|
|
||||||
createStackAndSetFrame(threadProxy, { executionStack.createStackFrame(stackFrameItem.frame) }, isCurrentContext)
|
createStackAndSetFrame(threadProxy, { executionStack.createStackFrame(stackFrameItem.frame) }, isCurrentContext)
|
||||||
}
|
}
|
||||||
is CreationCoroutineStackFrameItem -> {
|
is CreationCoroutineStackFrameItem -> {
|
||||||
@@ -73,15 +69,17 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
|
|||||||
val threadProxy = suspendContext.thread ?: return false
|
val threadProxy = suspendContext.thread ?: return false
|
||||||
createStackAndSetFrame(threadProxy, {
|
createStackAndSetFrame(threadProxy, {
|
||||||
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
||||||
SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), emptyList(), position)
|
SyntheticStackFrame(stackFrameItem.descriptor(realFrame), emptyList(), position)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
is SuspendCoroutineStackFrameItem -> {
|
is SuspendCoroutineStackFrameItem -> {
|
||||||
val threadProxy = suspendContext.thread ?: return false
|
val threadProxy = suspendContext.thread ?: return false
|
||||||
val lastFrame = valueContainer.infoData.lastObservedFrameFieldRef ?: return false
|
val position = stackFrameItem.location.findPosition(session.project)
|
||||||
|
?: return false
|
||||||
|
|
||||||
createStackAndSetFrame(threadProxy, {
|
createStackAndSetFrame(threadProxy, {
|
||||||
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
||||||
createSyntheticStackFrame(suspendContext, stackFrameItem, realFrame, lastFrame)
|
SyntheticStackFrame(stackFrameItem.descriptor(realFrame), stackFrameItem.spilledVariables, position)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
is RestoredCoroutineStackFrameItem -> {
|
is RestoredCoroutineStackFrameItem -> {
|
||||||
@@ -89,16 +87,21 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
|
|||||||
val position = stackFrameItem.location.findPosition(session.project)
|
val position = stackFrameItem.location.findPosition(session.project)
|
||||||
?: return false
|
?: return false
|
||||||
createStackAndSetFrame(threadProxy, {
|
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 threadProxy = suspendContext.thread ?: return false
|
||||||
val position = stackFrameItem.location.findPosition(session.project)
|
val position = stackFrameItem.location.findPosition(session.project)
|
||||||
?: return false
|
?: return false
|
||||||
createStackAndSetFrame(threadProxy, {
|
createStackAndSetFrame(threadProxy, {
|
||||||
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
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 -> {
|
else -> {
|
||||||
@@ -124,39 +127,18 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
|
|||||||
fun setCurrentStackFrame(stackFrameStack: XStackFrameStack) {
|
fun setCurrentStackFrame(stackFrameStack: XStackFrameStack) {
|
||||||
applicationThreadExecutor.schedule(
|
applicationThreadExecutor.schedule(
|
||||||
{
|
{
|
||||||
session.setCurrentStackFrame(stackFrameStack.executionStack, stackFrameStack.stackFrame)
|
session.setCurrentStackFrame(stackFrameStack.executionStack, stackFrameStack.stackFrame, false)
|
||||||
}, tree
|
}, 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 {
|
private fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false): XExecutionStack {
|
||||||
val executionStack = JavaExecutionStack(proxy, debugProcess, isCurrentContext)
|
val executionStack = JavaExecutionStack(proxy, debugProcess, isCurrentContext)
|
||||||
executionStack.initTopFrame()
|
executionStack.initTopFrame()
|
||||||
return executionStack
|
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?) {
|
data class KeyMouseEvent(val keyEvent: KeyEvent?, val mouseEvent: MouseEvent?) {
|
||||||
|
|||||||
+7
-28
@@ -9,10 +9,9 @@ import com.intellij.debugger.engine.AsyncStackTraceProvider
|
|||||||
import com.intellij.debugger.engine.JavaValue
|
import com.intellij.debugger.engine.JavaValue
|
||||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import com.intellij.execution.process.ProcessOutputTypes
|
import com.intellij.execution.process.ProcessOutputTypes
|
||||||
import com.intellij.openapi.extensions.Extensions
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isPreFlight
|
||||||
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
|
||||||
import org.jetbrains.kotlin.utils.getSafe
|
import org.jetbrains.kotlin.utils.getSafe
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
@@ -26,18 +25,15 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) {
|
override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) {
|
||||||
val asyncStackTraceProvider = getAsyncStackTraceProvider()
|
|
||||||
if (asyncStackTraceProvider == null) {
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
doOnBreakpoint {
|
doOnBreakpoint {
|
||||||
val frameProxy = this.frameProxy
|
val frameProxy = this.frameProxy
|
||||||
if (frameProxy != null) {
|
if (frameProxy != null) {
|
||||||
try {
|
try {
|
||||||
val coroutineInfoData =
|
val sem = frameProxy.location().isPreFlight()
|
||||||
asyncStackTraceProvider.lookupForResumeContinuation(frameProxy, this, emptyList())?.coroutineInfoData
|
val coroutineInfoData = if (sem.isCoroutineFound())
|
||||||
|
CoroutineFrameBuilder.lookupContinuation(this, frameProxy, sem)?.coroutineInfoData
|
||||||
|
else
|
||||||
|
null
|
||||||
if (coroutineInfoData != null && coroutineInfoData.stackTrace.isNotEmpty()) {
|
if (coroutineInfoData != null && coroutineInfoData.stackTrace.isNotEmpty()) {
|
||||||
print(renderAsyncStackTrace(coroutineInfoData.stackTrace), ProcessOutputTypes.SYSTEM)
|
print(renderAsyncStackTrace(coroutineInfoData.stackTrace), ProcessOutputTypes.SYSTEM)
|
||||||
} else {
|
} 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<Any>(ASYNC_STACKTRACE_EP_NAME)
|
|
||||||
val provider = extensionPoint.extensions.firstIsInstanceOrNull<CoroutineAsyncStackTraceProvider>()
|
|
||||||
|
|
||||||
if (provider == null) {
|
|
||||||
System.err.println("Kotlin coroutine async stack trace provider is not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Throwable.stackTraceAsString(): String {
|
private fun Throwable.stackTraceAsString(): String {
|
||||||
val writer = StringWriter()
|
val writer = StringWriter()
|
||||||
printStackTrace(PrintWriter(writer))
|
printStackTrace(PrintWriter(writer))
|
||||||
|
|||||||
+58
@@ -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<XStackFrame> = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -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<XStackFrame> = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-2
@@ -10,6 +10,7 @@ import com.intellij.openapi.module.Module
|
|||||||
import com.intellij.openapi.roots.LibraryOrderEntry
|
import com.intellij.openapi.roots.LibraryOrderEntry
|
||||||
import com.intellij.openapi.roots.ModuleRootManager
|
import com.intellij.openapi.roots.ModuleRootManager
|
||||||
import com.intellij.openapi.roots.OrderRootType
|
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.util.io.FileUtil
|
||||||
import com.intellij.openapi.vfs.LocalFileSystem
|
import com.intellij.openapi.vfs.LocalFileSystem
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
@@ -39,6 +40,7 @@ class DebuggerTestCompilerFacility(files: List<TestFile>, private val jvmTarget:
|
|||||||
|
|
||||||
private val mainFiles: TestFilesByLanguage
|
private val mainFiles: TestFilesByLanguage
|
||||||
private val libraryFiles: TestFilesByLanguage
|
private val libraryFiles: TestFilesByLanguage
|
||||||
|
private val mavenArtifacts = mutableListOf<String>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val splitFiles = splitByTarget(files)
|
val splitFiles = splitByTarget(files)
|
||||||
@@ -61,6 +63,15 @@ class DebuggerTestCompilerFacility(files: List<TestFile>, private val jvmTarget:
|
|||||||
compileLibrary(libraryFiles, srcDir, classesDir)
|
compileLibrary(libraryFiles, srcDir, classesDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addDependencies(libraryPaths: List<String>) {
|
||||||
|
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) {
|
fun compileLibrary(srcDir: File, classesDir: File) {
|
||||||
compileLibrary(this.libraryFiles, srcDir, classesDir)
|
compileLibrary(this.libraryFiles, srcDir, classesDir)
|
||||||
|
|
||||||
@@ -72,19 +83,22 @@ class DebuggerTestCompilerFacility(files: List<TestFile>, private val jvmTarget:
|
|||||||
resources.copy(classesDir)
|
resources.copy(classesDir)
|
||||||
(kotlin + java).copy(srcDir)
|
(kotlin + java).copy(srcDir)
|
||||||
|
|
||||||
|
if (kotlinStdlibInMavenArtifacts() == null)
|
||||||
|
mavenArtifacts.add(kotlinStdlibPath)
|
||||||
|
|
||||||
if (kotlin.isNotEmpty()) {
|
if (kotlin.isNotEmpty()) {
|
||||||
MockLibraryUtil.compileKotlin(
|
MockLibraryUtil.compileKotlin(
|
||||||
srcDir.absolutePath,
|
srcDir.absolutePath,
|
||||||
classesDir,
|
classesDir,
|
||||||
listOf("-jvm-target", jvmTarget.description),
|
listOf("-jvm-target", jvmTarget.description),
|
||||||
kotlinStdlibPath
|
*(mavenArtifacts.toTypedArray())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (java.isNotEmpty()) {
|
if (java.isNotEmpty()) {
|
||||||
CodegenTestUtil.compileJava(
|
CodegenTestUtil.compileJava(
|
||||||
java.map { File(srcDir, it.name).absolutePath },
|
java.map { File(srcDir, it.name).absolutePath },
|
||||||
listOf(kotlinStdlibPath, classesDir.absolutePath),
|
mavenArtifacts + classesDir.absolutePath,
|
||||||
listOf("-g"),
|
listOf("-g"),
|
||||||
classesDir
|
classesDir
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-3
@@ -9,6 +9,8 @@ import com.intellij.debugger.impl.DescriptorTestCase
|
|||||||
import com.intellij.debugger.impl.OutputChecker
|
import com.intellij.debugger.impl.OutputChecker
|
||||||
import com.intellij.execution.configurations.JavaParameters
|
import com.intellij.execution.configurations.JavaParameters
|
||||||
import com.intellij.execution.process.ProcessOutputTypes
|
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.LibraryOrderEntry
|
||||||
import com.intellij.openapi.roots.ModifiableRootModel
|
import com.intellij.openapi.roots.ModifiableRootModel
|
||||||
import com.intellij.openapi.roots.ModuleRootManager
|
import com.intellij.openapi.roots.ModuleRootManager
|
||||||
@@ -21,6 +23,7 @@ import com.intellij.openapi.vfs.VfsUtil
|
|||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.testFramework.EdtTestUtil
|
import com.intellij.testFramework.EdtTestUtil
|
||||||
import com.intellij.xdebugger.XDebugSession
|
import com.intellij.xdebugger.XDebugSession
|
||||||
|
import org.jetbrains.idea.maven.aether.ArtifactKind
|
||||||
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
||||||
import org.jetbrains.kotlin.config.JvmTarget
|
import org.jetbrains.kotlin.config.JvmTarget
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
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.jetbrains.kotlin.test.testFramework.runWriteAction
|
||||||
import org.junit.ComparisonFailure
|
import org.junit.ComparisonFailure
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor as JpsMavenRepositoryLibraryDescriptor
|
||||||
|
|
||||||
internal const val KOTLIN_LIBRARY_NAME = "KotlinLibrary"
|
internal const val KOTLIN_LIBRARY_NAME = "KotlinLibrary"
|
||||||
internal const val TEST_LIBRARY_NAME = "TestLibrary"
|
internal const val TEST_LIBRARY_NAME = "TestLibrary"
|
||||||
@@ -109,7 +113,10 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() {
|
|||||||
val compilerFacility = DebuggerTestCompilerFacility(testFiles, jvmTarget)
|
val compilerFacility = DebuggerTestCompilerFacility(testFiles, jvmTarget)
|
||||||
|
|
||||||
for (library in preferences[DebuggerPreferenceKeys.ATTACH_LIBRARY]) {
|
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)
|
compilerFacility.compileLibrary(librarySrcDirectory, libraryOutputDirectory)
|
||||||
@@ -125,6 +132,9 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() {
|
|||||||
doMultiFileTest(testFiles, preferences)
|
doMultiFileTest(testFiles, preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun addMavenDependency(compilerFacility: DebuggerTestCompilerFacility, library: String) {
|
||||||
|
}
|
||||||
|
|
||||||
private fun createTestFiles(wholeFile: File, wholeFileContents: String): TestFiles {
|
private fun createTestFiles(wholeFile: File, wholeFileContents: String): TestFiles {
|
||||||
val testFiles = org.jetbrains.kotlin.test.TestFiles.createTestFiles(
|
val testFiles = org.jetbrains.kotlin.test.TestFiles.createTestFiles(
|
||||||
wholeFile.name,
|
wholeFile.name,
|
||||||
@@ -227,6 +237,5 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() {
|
|||||||
return super.shouldRunTest() && !isIgnoredInDatabaseWithLog(this)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+146
@@ -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<JpsMavenRepositoryLibraryDescriptor>()
|
||||||
|
val classPath = mutableListOf<String>()
|
||||||
|
|
||||||
|
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<String>()
|
||||||
|
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<Any>(ASYNC_STACKTRACE_EP_NAME)
|
||||||
|
val provider = extensionPoint.extensions.firstIsInstanceOrNull<CoroutineAsyncStackTraceProvider>()
|
||||||
|
|
||||||
|
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<OrderRoot>) =
|
||||||
|
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<OrderRoot> {
|
||||||
|
return JarRepositoryManager.loadDependenciesSync(
|
||||||
|
project, description, setOf(ArtifactKind.ARTIFACT),
|
||||||
|
RemoteRepositoryDescription.DEFAULT_REPOSITORIES, null
|
||||||
|
) ?: throw AssertionError("Maven Dependency not found: $description")
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -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<XStackFrame> collectFrames(@NotNull XExecutionStack thread) {
|
||||||
|
return collectFrames(thread, TIMEOUT_MS * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<XStackFrame> collectFrames(XExecutionStack thread, long timeout) {
|
||||||
|
return collectFrames(thread, timeout, XDebuggerTestUtil::waitFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<XStackFrame> collectFrames(XExecutionStack thread, long timeout, BiFunction<Semaphore, Long, Boolean> 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<List<XStackFrame>, String> collectFramesWithError(XExecutionStack thread, long timeout, BiFunction<Semaphore, Long, Boolean> 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<XStackFrame> implements XStackFrameContainerEx {
|
||||||
|
public volatile XStackFrame frameToSelect;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addStackFrames(@NotNull List<? extends XStackFrame> stackFrames, boolean last) {
|
||||||
|
addChildren(stackFrames, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addStackFrames(@NotNull List<? extends XStackFrame> 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<XValue> collectChildren(XValueContainer value) {
|
||||||
|
return collectChildren(value, XDebuggerTestUtil::waitFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static List<XValue> collectChildren(XValueContainer value, BiFunction<Semaphore, Long, Boolean> waitFunction) {
|
||||||
|
final Pair<List<XValue>, String> childrenWithError = collectChildrenWithError(value, waitFunction);
|
||||||
|
final String error = childrenWithError.second;
|
||||||
|
assertNull("Error getting children: " + error, error);
|
||||||
|
return childrenWithError.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static Pair<List<XValue>, String> collectChildrenWithError(XValueContainer value) {
|
||||||
|
return collectChildrenWithError(value, XDebuggerTestUtil::waitFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static Pair<List<XValue>, String> collectChildrenWithError(XValueContainer value,
|
||||||
|
BiFunction<Semaphore, Long, Boolean> waitFunction) {
|
||||||
|
XTestCompositeNode container = new XTestCompositeNode();
|
||||||
|
value.computeChildren(container);
|
||||||
|
|
||||||
|
return container.waitFor(TIMEOUT_MS, waitFunction);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -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<XValue> implements XCompositeNode {
|
||||||
|
@Override
|
||||||
|
public void addChildren(@NotNull XValueChildrenList children, boolean last) {
|
||||||
|
final List<XValue> 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
@@ -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<T> {
|
||||||
|
private final List<T> myChildren = new SmartList<>();
|
||||||
|
private String myErrorMessage;
|
||||||
|
private final Semaphore myFinished = new Semaphore(0);
|
||||||
|
|
||||||
|
public void addChildren(List<? extends T> 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<List<T>, String> waitFor(long timeoutMs) {
|
||||||
|
return waitFor(timeoutMs, (semaphore, timeout) -> XDebuggerTestUtil.waitFor(myFinished, timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Pair<List<T>, String> waitFor(long timeoutMs, BiFunction<? super Semaphore, ? super Long, Boolean> waitFunction) {
|
||||||
|
if (!waitFunction.apply(myFinished, timeoutMs)) {
|
||||||
|
throw new AssertionError("Waiting timed out");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair.create(myChildren, myErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package coroutine1
|
||||||
|
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
import kotlin.coroutines.startCoroutine
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val cnt = Continuation<Int>(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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package continuation
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val a = "a"
|
||||||
|
fibonacci().take(10).toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextSequence(terms: Pair<Int, Int>): Pair<Int, Int> {
|
||||||
|
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++
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
+30
@@ -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!
|
||||||
|
}
|
||||||
+47
@@ -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
|
||||||
Reference in New Issue
Block a user