(CoroutineDebugger) negative frames removed from coroutine stack

This commit is contained in:
Vladimir Ilmov
2020-04-20 11:05:15 +02:00
parent be2185ec01
commit 0ce8ca7bcd
7 changed files with 87 additions and 27 deletions
@@ -10,18 +10,21 @@ 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
class CoroutineFrameBuilder {
companion object {
val log by logger
const val PRE_FETCH_FRAME_COUNT = 5
fun build(coroutine: CoroutineInfoData, suspendContext: SuspendContextImpl): DoubleFrameList? =
when {
@@ -39,12 +42,12 @@ class CoroutineFrameBuilder {
for (runningStackFrameProxy in realFrames) {
val preflightStackFrame = coroutineExitFrame(runningStackFrameProxy, suspendContext)
if (preflightStackFrame != null) {
coroutineStackFrameList.add(buildRealStackFrameItem(preflightStackFrame.stackFrameProxy,))
coroutineStackFrameList.addIfNotNull(buildRealStackFrameItem(preflightStackFrame.stackFrameProxy))
val doubleFrameList = build(preflightStackFrame, suspendContext)
coroutineStackFrameList.addAll(doubleFrameList.stackTrace)
return DoubleFrameList(coroutineStackFrameList, doubleFrameList.creationStackTrace)
} else {
coroutineStackFrameList.add(buildRealStackFrameItem(runningStackFrameProxy,))
coroutineStackFrameList.addIfNotNull(buildRealStackFrameItem(runningStackFrameProxy))
}
}
return DoubleFrameList(coroutineStackFrameList, emptyList())
@@ -59,7 +62,8 @@ class CoroutineFrameBuilder {
stackFrames.addAll(preflightFrame.restoredStackTrace())
// rest of the stack
stackFrames.addAll(preflightFrame.threadPreCoroutineFrames.drop(1).mapIndexedNotNull { index, stackFrameProxyImpl ->
val framesLeft = preflightFrame.threadPreCoroutineFrames.drop(1)
stackFrames.addAll(framesLeft.mapIndexedNotNull { index, stackFrameProxyImpl ->
suspendContext.invokeInManagerThread { buildRealStackFrameItem(stackFrameProxyImpl) }
})
@@ -73,9 +77,12 @@ class CoroutineFrameBuilder {
private fun buildRealStackFrameItem(
frame: StackFrameProxyImpl
): RunningCoroutineStackFrameItem {
): RunningCoroutineStackFrameItem? {
val location = frame.location()
return RunningCoroutineStackFrameItem(frame, location)
if (!location.safeCoroutineExitPointLineNumber())
return RunningCoroutineStackFrameItem(frame, location)
else
return null
}
/**
@@ -113,10 +120,7 @@ class CoroutineFrameBuilder {
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?
val invokeSuspendFrame = lookForTheFollowingFrame(theFollowingFrames) ?: return null
} else
return null
}
@@ -135,6 +139,16 @@ class CoroutineFrameBuilder {
return null
}
private fun lookForTheFollowingFrame(theFollowingFrames: List<StackFrameProxyImpl>): StackFrameProxyImpl? {
for (i in 0 until min(PRE_FETCH_FRAME_COUNT, theFollowingFrames.size)) { // pre-scan PRE_FETCH_FRAME_COUNT frames
val nextFrame = theFollowingFrames.get(i)
if (nextFrame.location().isPreFlight() == SuspendExitMode.SUSPEND_METHOD) {
return nextFrame
}
}
return null
}
private fun preflight(
frame: StackFrameProxyImpl,
framesLeft: List<StackFrameProxyImpl>,
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.util
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.openapi.project.Project
@@ -26,6 +27,9 @@ const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as c
fun Method.isInvokeSuspend(): Boolean =
name() == "invokeSuspend" && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;"
fun Method.isInvoke(): Boolean =
name() == "invoke" && signature().contains("Ljava/lang/Object;)Ljava/lang/Object;")
fun Method.isContinuation() =
isInvokeSuspend() && declaringType().isContinuation() /* Perhaps need to check for "Lkotlin/coroutines/Continuation;)" in signature() ? */
@@ -44,11 +48,14 @@ fun Location.isPreFlight(): SuspendExitMode {
return SuspendExitMode.SUSPEND_LAMBDA
else if (method.hasContinuationParameter())
return SuspendExitMode.SUSPEND_METHOD_PARAMETER
else if (method.isInvokeSuspend() && safeLineNumber() == -1)
else if ((method.isInvokeSuspend() || method.isInvoke()) && safeCoroutineExitPointLineNumber())
return SuspendExitMode.SUSPEND_METHOD
return SuspendExitMode.NONE
}
fun Location.safeCoroutineExitPointLineNumber() =
wrapIllegalArgumentException { DebuggerUtilsEx.getLineNumber(this, false) } ?: -2 == -1
fun ReferenceType.isContinuation() =
isBaseContinuationImpl() || isSubtype("kotlin.coroutines.Continuation")