(CoroutineDebugger) Minor fixes, kotlinx.coroutines version fix

This commit is contained in:
Vladimir Ilmov
2020-04-20 16:32:29 +02:00
parent 0ce8ca7bcd
commit 73f5e48518
22 changed files with 197 additions and 179 deletions
@@ -6,10 +6,8 @@
package org.jetbrains.kotlin.idea.debugger.coroutine
import com.intellij.debugger.DebuggerInvocationUtil
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaDebugProcess
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.execution.configurations.DebuggingRunnerData
import com.intellij.execution.configurations.JavaParameters
import com.intellij.execution.configurations.RunConfigurationBase
@@ -18,7 +16,6 @@ import com.intellij.execution.ui.layout.PlaceInGrid
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.content.Content
import com.intellij.util.messages.MessageBusConnection
import com.intellij.xdebugger.XDebugProcess
@@ -49,15 +46,15 @@ class DebuggerConnection(
val kotlinxCoroutinesDebug = params.classPath?.pathList?.firstOrNull { it.contains("kotlinx-coroutines-debug") }
val mode = if (kotlinxCoroutinesDebug != null) {
CoroutineDebuggerMode.VERSION_UP_TO_1_3_4
CoroutineDebuggerMode.VERSION_UP_TO_1_3_5
} else if (kotlinxCoroutinesCore != null) {
determineCoreVersionMode(kotlinxCoroutinesCore)
} else
CoroutineDebuggerMode.DISABLED
when (mode) {
CoroutineDebuggerMode.VERSION_1_3_5_AND_UP -> initializeCoroutineAgent(params, kotlinxCoroutinesCore)
CoroutineDebuggerMode.VERSION_UP_TO_1_3_4 -> initializeCoroutineAgent(params, kotlinxCoroutinesDebug)
CoroutineDebuggerMode.VERSION_1_3_6_AND_UP -> initializeCoroutineAgent(params, kotlinxCoroutinesCore)
CoroutineDebuggerMode.VERSION_UP_TO_1_3_5 -> initializeCoroutineAgent(params, kotlinxCoroutinesDebug)
else -> log.debug("CoroutineDebugger disabled.")
}
}
@@ -69,9 +66,9 @@ class DebuggerConnection(
val matchResult = regex.matchEntire(kotlinxCoroutinesCore) ?: return CoroutineDebuggerMode.DISABLED
val coroutinesCoreVersion = DefaultArtifactVersion(matchResult.groupValues.get(1))
val versionToCompareTo = DefaultArtifactVersion("1.3.4")
val versionToCompareTo = DefaultArtifactVersion("1.3.5")
return if (versionToCompareTo.compareTo(coroutinesCoreVersion) < 0)
CoroutineDebuggerMode.VERSION_1_3_5_AND_UP
CoroutineDebuggerMode.VERSION_1_3_6_AND_UP
else
CoroutineDebuggerMode.DISABLED
}
@@ -96,7 +93,7 @@ class DebuggerConnection(
override fun processStopped(debugProcess: XDebugProcess) {
val rootDisposable = disposable
if (rootDisposable is Disposable && debugProcess is JavaDebugProcess && debugProcess.session.suspendContext is SuspendContextImpl) {
ManagerThreadExecutor(debugProcess).on(debugProcess.session.suspendContext).schedule {
ManagerThreadExecutor(debugProcess).on(debugProcess.session.suspendContext).invoke {
Disposer.dispose(rootDisposable)
disposable = null
}
@@ -124,6 +121,6 @@ class DebuggerConnection(
enum class CoroutineDebuggerMode {
DISABLED,
VERSION_UP_TO_1_3_4,
VERSION_1_3_5_AND_UP
VERSION_UP_TO_1_3_5,
VERSION_1_3_6_AND_UP
}
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
import com.sun.jdi.ObjectReference
import com.sun.jdi.ThreadReference
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData.Companion.DEFAULT_COROUTINE_NAME
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData.Companion.DEFAULT_COROUTINE_STATE
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
@@ -56,9 +58,9 @@ data class CoroutineNameIdState(val name: String, val id: String, val state: Sta
companion object {
fun instance(mirror: MirrorOfCoroutineInfo): CoroutineNameIdState =
CoroutineNameIdState(
mirror.context?.name ?: "coroutine",
mirror.context?.name ?: DEFAULT_COROUTINE_NAME,
"${mirror.sequenceNumber}",
State.valueOf(mirror.state ?: "UNKNOWN"),
State.valueOf(mirror.state ?: DEFAULT_COROUTINE_STATE),
mirror.context?.dispatcher
)
}
@@ -49,14 +49,12 @@ class CoroutinePreflightStackFrame(
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
// skip restored variables
return super.getVisibleVariables().filter { v -> firstFrameVariables.find { it.name == v.name() } == null }
return super.getVisibleVariables().filter { v -> ! firstFrameVariables.any { it.name == v.name() } }
}
override fun isInLibraryContent() =
false
override fun isInLibraryContent() = false
override fun isSynthetic() =
false
override fun isSynthetic() = false
fun restoredStackTrace() =
coroutineInfoData.restoredStackTrace(mode)
@@ -69,7 +67,7 @@ enum class SuspendExitMode {
this == SUSPEND_LAMBDA || this == SUSPEND_METHOD_PARAMETER
fun isSuspendMethodParameter() =
this == SuspendExitMode.SUSPEND_METHOD_PARAMETER
this == SUSPEND_METHOD_PARAMETER
}
class CreationCoroutineStackFrame(debugProcess: DebugProcessImpl, item: StackFrameItem) : CoroutineStackFrame(debugProcess, item) {
@@ -81,11 +79,7 @@ class CreationCoroutineStackFrame(debugProcess: DebugProcessImpl, item: StackFra
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)
@@ -93,7 +87,6 @@ open class CoroutineStackFrame(debugProcess: DebugProcessImpl, val item: StackFr
super.computeChildren(node)
}
override fun getCaptionAboveOf() = "CoroutineExit"
override fun hasSeparatorAbove(): Boolean =
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
import org.jetbrains.kotlin.idea.debugger.coroutine.util.*
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class ContinuationHolder(val context: DefaultExecutionContext) {
class ContinuationHolder private constructor(val context: DefaultExecutionContext) {
private val debugMetadata: DebugMetadata? = DebugMetadata.instance(context)
private val locationCache = LocationCache(context)
private val debugProbesImpl = DebugProbesImpl.instance(context)
@@ -70,6 +70,7 @@ class ContinuationHolder(val context: DefaultExecutionContext) {
val id = standAloneCoroutineMirror.context.id
val name = standAloneCoroutineMirror.context.name ?: CoroutineInfoData.DEFAULT_COROUTINE_NAME
val toString = reference.string(value, context)
// trying to get coroutine information by calling JobSupport.toString(), ${nameString()}{${stateString(state)}}@$hexAddress
val r = """\w+\{(\w+)\}\@([\w\d]+)""".toRegex()
val matcher = r.toPattern().matcher(toString)
if (matcher.matches()) {
@@ -94,6 +95,9 @@ class ContinuationHolder(val context: DefaultExecutionContext) {
companion object {
val log by logger
fun instance(context: DefaultExecutionContext) =
ContinuationHolder(context)
private fun stateOf(state: String?): State =
when (state) {
"Active" -> State.RUNNING
@@ -104,8 +108,6 @@ class ContinuationHolder(val context: DefaultExecutionContext) {
"New" -> State.NEW
else -> State.UNKNOWN
}
}
}
@@ -129,5 +131,4 @@ fun FieldVariable.toJavaValue(continuation: ObjectReference, context: DefaultExe
context.debugProcess.xdebugProcess!!.nodeManager,
false
)
}
@@ -18,7 +18,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : CoroutineInfoProvider {
val log by logger
val debugMetadataKtType = executionContext.findCoroutineMetadataType()
val holder = ContinuationHolder(executionContext)
val holder = ContinuationHolder.instance(executionContext)
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
val vm = executionContext.vm
@@ -55,7 +55,7 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
): CoroutineInfoData? {
val mirror = ccMirrorProvider.mirror(dispatchedContinuation, executionContext) ?: return null
val continuation = mirror.delegate?.continuation ?: return null
return holder.extractCoroutineInfoData(continuation)
return holder?.extractCoroutineInfoData(continuation)
}
private fun dispatchedContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
@@ -75,7 +75,7 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
private fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? {
debugMetadataKtType ?: return null
val initialContinuation = dispatchedContinuation.getValue(continuation) as ObjectReference
return holder.extractCoroutineInfoData(initialContinuation)
return holder?.extractCoroutineInfoData(initialContinuation)
}
}
@@ -34,16 +34,18 @@ class ManagerThreadExecutor(val debugProcess: DebugProcessImpl) {
constructor(sc: XSuspendContext, priority: PrioritizedTask.Priority) : this(sc as SuspendContextImpl, priority)
fun schedule(f: (SuspendContextImpl) -> Unit) {
val suspendContextCommand = object : SuspendContextCommandImpl(suspendContext) {
fun invoke(f: (SuspendContextImpl) -> Unit) {
debugProcess.managerThread.invoke(makeCommand(f))
}
private fun makeCommand(f: (SuspendContextImpl) -> Unit) =
object : SuspendContextCommandImpl(suspendContext) {
override fun getPriority() = this@ManagerThreadExecutorInstance.priority
override fun contextAction(suspendContext: SuspendContextImpl) {
f(suspendContext)
}
}
debugProcess.managerThread.invoke(suspendContextCommand)
}
}
}
@@ -53,7 +55,7 @@ class ApplicationThreadExecutor {
}
fun schedule(f: () -> Unit, component: Component) {
return ApplicationManager.getApplication().invokeLater( { f() }, ModalityState.stateForComponent(component))
return ApplicationManager.getApplication().invokeLater({ f() }, ModalityState.stateForComponent(component))
}
fun schedule(f: () -> Unit) {
@@ -18,10 +18,10 @@ abstract class BaseDynamicMirror<T>(val value: ObjectReference, val name: String
fun makeField(fieldName: String): Field? =
cls?.let { it.fieldByName(fieldName) }
fun makeMethod(methodName: String): Method? =
fun findMethod(methodName: String): Method? =
cls?.let { it.methodsByName(methodName).single() }
fun makeMethod(methodName: String, signature: String): Method? =
fun findMethod(methodName: String, signature: String): Method? =
cls?.let { it.methodsByName(methodName, signature).single() }
fun isCompatible(value: ObjectReference?) =
@@ -170,8 +170,8 @@ data class MirrorOfCoroutineInfo(
class CoroutineStackFrame(value: ObjectReference, context: DefaultExecutionContext) :
BaseDynamicMirror<MirrorOfCoroutineStackFrame>(value, "kotlin.coroutines.jvm.internal.CoroutineStackFrame", context) {
private val stackTraceElementMirror = StackTraceElement(context)
private val callerFrameMethod = makeMethod("getCallerFrame")
private val getStackTraceElementMethod = makeMethod("getStackTraceElement")
private val callerFrameMethod = findMethod("getCallerFrame")
private val getStackTraceElementMethod = findMethod("getStackTraceElement")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineStackFrame? {
val objectReference = objectValue(value, callerFrameMethod, context)
@@ -6,10 +6,8 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
import com.sun.jdi.ArrayReference
import com.sun.jdi.ClassType
import com.sun.jdi.ObjectReference
import com.sun.jdi.StringReference
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isBaseContinuationImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
@@ -20,10 +18,8 @@ class DebugMetadata private constructor(context: DefaultExecutionContext) :
makeMethod("getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;")
val baseContinuationImpl = BaseContinuationImpl(context, this)
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? {
// @TODO fix this
return null
}
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? =
throw IllegalStateException("Not meant to be mirrored.")
fun fetchContinuationStack(continuation: ObjectReference, context: DefaultExecutionContext): MirrorOfContinuationStack {
val coroutineStack = mutableListOf<MirrorOfStackFrame>()
@@ -45,13 +41,14 @@ class DebugMetadata private constructor(context: DefaultExecutionContext) :
companion object {
val log by logger
fun instance(context: DefaultExecutionContext) =
fun instance(context: DefaultExecutionContext): DebugMetadata? {
try {
DebugMetadata(context)
return DebugMetadata(context)
} catch (e: IllegalStateException) {
log.warn("Attempt to access DebugMetadata", e)
null
}
return null
}
}
}
@@ -87,7 +84,8 @@ class BaseContinuationImpl(context: DefaultExecutionContext, private val debugMe
val length = getSpilledVariableFieldMappingReference.length() / 2
val fieldVariables = ArrayList<FieldVariable>()
for (index in 0 until length) {
fieldVariables.add(getFieldVariableName(getSpilledVariableFieldMappingReference, index) ?: continue)
var fieldVariable = getFieldVariableName(getSpilledVariableFieldMappingReference, index) ?: continue
fieldVariables.add(fieldVariable)
}
return fieldVariables
}
@@ -10,13 +10,9 @@ 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.util.containers.addIfNotNull
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
import java.lang.Integer.min
@@ -42,12 +38,17 @@ class CoroutineFrameBuilder {
for (runningStackFrameProxy in realFrames) {
val preflightStackFrame = coroutineExitFrame(runningStackFrameProxy, suspendContext)
if (preflightStackFrame != null) {
coroutineStackFrameList.addIfNotNull(buildRealStackFrameItem(preflightStackFrame.stackFrameProxy))
buildRealStackFrameItem(preflightStackFrame.stackFrameProxy)?.let {
coroutineStackFrameList.add(it)
}
val doubleFrameList = build(preflightStackFrame, suspendContext)
coroutineStackFrameList.addAll(doubleFrameList.stackTrace)
return DoubleFrameList(coroutineStackFrameList, doubleFrameList.creationStackTrace)
} else {
coroutineStackFrameList.addIfNotNull(buildRealStackFrameItem(runningStackFrameProxy))
buildRealStackFrameItem(runningStackFrameProxy)?.let {
coroutineStackFrameList.add(it)
}
}
}
return DoubleFrameList(coroutineStackFrameList, emptyList())
@@ -62,6 +63,7 @@ class CoroutineFrameBuilder {
stackFrames.addAll(preflightFrame.restoredStackTrace())
// rest of the stack
// @TODO perhaps we need to merge the dropped frame below with the last restored (at least variables).
val framesLeft = preflightFrame.threadPreCoroutineFrames.drop(1)
stackFrames.addAll(framesLeft.mapIndexedNotNull { index, stackFrameProxyImpl ->
suspendContext.invokeInManagerThread { buildRealStackFrameItem(stackFrameProxyImpl) }
@@ -95,17 +97,11 @@ class CoroutineFrameBuilder {
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(
@@ -117,13 +113,14 @@ class CoroutineFrameBuilder {
return null
val theFollowingFrames = theFollowingFrames(frame) ?: emptyList()
if (mode.isSuspendMethodParameter()) {
val suspendParameterFrame = if (mode.isSuspendMethodParameter()) {
if (theFollowingFrames.isNotEmpty()) {
// have to check next frame if that's invokeSuspend:-1 before proceed, otherwise skip
val invokeSuspendFrame = lookForTheFollowingFrame(theFollowingFrames) ?: return null
lookForTheFollowingFrame(theFollowingFrames) ?: return null
} else
return null
}
} else
null
if (threadAndContextSupportsEvaluation(suspendContext, frame)) {
val context = suspendContext.executionContext() ?: return null
@@ -133,7 +130,8 @@ class CoroutineFrameBuilder {
else -> null
} ?: return null
val coroutineInfo = ContinuationHolder(context).extractCoroutineInfoData(continuation) ?: return null
val continuationHolder = ContinuationHolder.instance(context) ?: return null
val coroutineInfo = continuationHolder.extractCoroutineInfoData(continuation) ?: return null
return preflight(frame, theFollowingFrames, coroutineInfo, mode)
}
return null
@@ -151,7 +151,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
inner class CoroutineGroupContainer(val suspendContext: SuspendContextImpl) : XValueContainer() {
override fun computeChildren(node: XCompositeNode) {
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
managerThreadExecutor.on(suspendContext).schedule {
managerThreadExecutor.on(suspendContext).invoke {
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
val emptyDispatcherName = KotlinDebuggerCoroutinesBundle.message("coroutine.view.dispatcher.empty")
@@ -210,7 +210,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
) : RendererContainer(renderer.render(infoData)) {
override fun computeChildren(node: XCompositeNode) {
managerThreadExecutor.on(suspendContext).schedule {
managerThreadExecutor.on(suspendContext).invoke {
val children = XValueChildrenList()
val doubleFrameList = CoroutineFrameBuilder.build(infoData, suspendContext)
doubleFrameList?.stackTrace?.forEach {