(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:
Vladimir Ilmov
2020-04-14 14:08:59 +02:00
parent 153056b4fd
commit 3cd0eb0fca
47 changed files with 1487 additions and 734 deletions
@@ -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") {
@@ -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…
@@ -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
} }
@@ -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;
} }
} }
@@ -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
}
} }
@@ -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")
@@ -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
}
}
@@ -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?) =
@@ -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())
@@ -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
}
} }
} }
@@ -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 {
@@ -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
}
@@ -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
)
}
@@ -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")
@@ -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,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
*/ */
@@ -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)
} }
} }
@@ -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
}
}
@@ -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
@@ -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)
@@ -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,
@@ -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 {
@@ -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?
)
@@ -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>)
@@ -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
}
}
}
@@ -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
@@ -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()
@@ -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
} }
} }
@@ -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) =
@@ -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)
} }
} }
@@ -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?) {
@@ -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))
@@ -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)
}
}
}
}
@@ -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)
}
}
}
}
@@ -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");
}
}
@@ -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
) )
@@ -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
} }
@@ -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")
}
}
@@ -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");
}
}
@@ -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);
}
}
@@ -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) {
}
}
@@ -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
@@ -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!
}
@@ -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