(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.jdi.ThreadReferenceProxyImpl
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.util.containers.addIfNotNull
import com.sun.jdi.ObjectReference 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.ContinuationHolder import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
import org.jetbrains.kotlin.idea.debugger.safeLineNumber import org.jetbrains.kotlin.idea.debugger.safeLineNumber
import org.jetbrains.kotlin.idea.debugger.safeLocation import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod import org.jetbrains.kotlin.idea.debugger.safeMethod
import java.lang.Integer.min
class CoroutineFrameBuilder { class CoroutineFrameBuilder {
companion object { companion object {
val log by logger val log by logger
const val PRE_FETCH_FRAME_COUNT = 5
fun build(coroutine: CoroutineInfoData, suspendContext: SuspendContextImpl): DoubleFrameList? = fun build(coroutine: CoroutineInfoData, suspendContext: SuspendContextImpl): DoubleFrameList? =
when { when {
@@ -39,12 +42,12 @@ class CoroutineFrameBuilder {
for (runningStackFrameProxy in realFrames) { for (runningStackFrameProxy in realFrames) {
val preflightStackFrame = coroutineExitFrame(runningStackFrameProxy, suspendContext) val preflightStackFrame = coroutineExitFrame(runningStackFrameProxy, suspendContext)
if (preflightStackFrame != null) { if (preflightStackFrame != null) {
coroutineStackFrameList.add(buildRealStackFrameItem(preflightStackFrame.stackFrameProxy,)) coroutineStackFrameList.addIfNotNull(buildRealStackFrameItem(preflightStackFrame.stackFrameProxy))
val doubleFrameList = build(preflightStackFrame, suspendContext) val doubleFrameList = build(preflightStackFrame, suspendContext)
coroutineStackFrameList.addAll(doubleFrameList.stackTrace) coroutineStackFrameList.addAll(doubleFrameList.stackTrace)
return DoubleFrameList(coroutineStackFrameList, doubleFrameList.creationStackTrace) return DoubleFrameList(coroutineStackFrameList, doubleFrameList.creationStackTrace)
} else { } else {
coroutineStackFrameList.add(buildRealStackFrameItem(runningStackFrameProxy,)) coroutineStackFrameList.addIfNotNull(buildRealStackFrameItem(runningStackFrameProxy))
} }
} }
return DoubleFrameList(coroutineStackFrameList, emptyList()) return DoubleFrameList(coroutineStackFrameList, emptyList())
@@ -59,7 +62,8 @@ class CoroutineFrameBuilder {
stackFrames.addAll(preflightFrame.restoredStackTrace()) stackFrames.addAll(preflightFrame.restoredStackTrace())
// rest of the stack // 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) } suspendContext.invokeInManagerThread { buildRealStackFrameItem(stackFrameProxyImpl) }
}) })
@@ -73,9 +77,12 @@ class CoroutineFrameBuilder {
private fun buildRealStackFrameItem( private fun buildRealStackFrameItem(
frame: StackFrameProxyImpl frame: StackFrameProxyImpl
): RunningCoroutineStackFrameItem { ): RunningCoroutineStackFrameItem? {
val location = frame.location() 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 (mode.isSuspendMethodParameter()) {
if (theFollowingFrames.isNotEmpty()) { if (theFollowingFrames.isNotEmpty()) {
// have to check next frame if that's invokeSuspend:-1 before proceed, otherwise skip // have to check next frame if that's invokeSuspend:-1 before proceed, otherwise skip
val theFollowingFrame = theFollowingFrames.first() val invokeSuspendFrame = lookForTheFollowingFrame(theFollowingFrames) ?: return null
val theFollowingMode = theFollowingFrame.location().isPreFlight()
if (theFollowingMode != SuspendExitMode.SUSPEND_METHOD)
return null // break, that's not the exit, perhaps the following one?
} else } else
return null return null
} }
@@ -135,6 +139,16 @@ class CoroutineFrameBuilder {
return null 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( private fun preflight(
frame: StackFrameProxyImpl, frame: StackFrameProxyImpl,
framesLeft: List<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.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.openapi.project.Project 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 = fun Method.isInvokeSuspend(): Boolean =
name() == "invokeSuspend" && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" 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() = fun Method.isContinuation() =
isInvokeSuspend() && declaringType().isContinuation() /* Perhaps need to check for "Lkotlin/coroutines/Continuation;)" in signature() ? */ 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 return SuspendExitMode.SUSPEND_LAMBDA
else if (method.hasContinuationParameter()) else if (method.hasContinuationParameter())
return SuspendExitMode.SUSPEND_METHOD_PARAMETER return SuspendExitMode.SUSPEND_METHOD_PARAMETER
else if (method.isInvokeSuspend() && safeLineNumber() == -1) else if ((method.isInvokeSuspend() || method.isInvoke()) && safeCoroutineExitPointLineNumber())
return SuspendExitMode.SUSPEND_METHOD return SuspendExitMode.SUSPEND_METHOD
return SuspendExitMode.NONE return SuspendExitMode.NONE
} }
fun Location.safeCoroutineExitPointLineNumber() =
wrapIllegalArgumentException { DebuggerUtilsEx.getLineNumber(this, false) } ?: -2 == -1
fun ReferenceType.isContinuation() = fun ReferenceType.isContinuation() =
isBaseContinuationImpl() || isSubtype("kotlin.coroutines.Continuation") isBaseContinuationImpl() || isSubtype("kotlin.coroutines.Continuation")
@@ -7,22 +7,19 @@ Thread stack trace:
($completion, b, bParam) ($completion, b, bParam)
a:21, SuspendFunKt (coroutine1) a:21, SuspendFunKt (coroutine1)
($completion, $continuation, $result, a, aParam) ($completion, $continuation, $result, a, aParam)
test1:15, SuspendFunKt (coroutine1) CoroutineNameIdState(name=coroutine, id=-1, state=UNKNOWN, dispatcher=null)
($completion, $continuation, $result, i, test1) test1:15, coroutine1.SuspendFunKt
invoke:9, SuspendFunKt$main$result$1 (coroutine1) (i, test1)
invoke:9, coroutine1.SuspendFunKt$main$result$1
(continuation, p1, this) (continuation, p1, this)
invoke:-1, SuspendFunKt$main$result$1 (coroutine1) invokeSuspend:121, kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3
(this)
invokeSuspend:121, IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3 (kotlin.coroutines.intrinsics)
($i$a$-createCoroutineFromSuspendFunction-IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$2, it, result, this) ($i$a$-createCoroutineFromSuspendFunction-IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$2, it, result, this)
resumeWith:33, BaseContinuationImpl (kotlin.coroutines.jvm.internal) resumeWith:33, kotlin.coroutines.jvm.internal.BaseContinuationImpl
($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this) ($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this)
startCoroutine:128, ContinuationKt (kotlin.coroutines) startCoroutine:128, kotlin.coroutines.ContinuationKt
($this$startCoroutine, completion, receiver) ($this$startCoroutine, completion, receiver)
main:9, SuspendFunKt (coroutine1) main:9, coroutine1.SuspendFunKt
(cnt) (cnt)
main:-1, SuspendFunKt (coroutine1)
()
Disconnected from the target VM Disconnected from the target VM
Process finished with exit code 0 Process finished with exit code 0
@@ -12,7 +12,7 @@ CoroutineNameIdState(name=coroutine, id=-1, state=UNKNOWN, dispatcher=null)
($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this) ($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this)
hasNext:140, kotlin.sequences.SequenceBuilderIterator hasNext:140, kotlin.sequences.SequenceBuilderIterator
(step, this) (step, this)
hasNext:374, kotlin.sequences.TakeSequence$iterator$1 hasNext:406, kotlin.sequences.TakeSequence$iterator$1
(this) (this)
toCollection:722, kotlin.sequences.SequencesKt___SequencesKt toCollection:722, kotlin.sequences.SequencesKt___SequencesKt
($this$toCollection, destination) ($this$toCollection, destination)
@@ -22,8 +22,6 @@ CoroutineNameIdState(name=coroutine, id=-1, state=UNKNOWN, dispatcher=null)
($this$toList) ($this$toList)
main:5, continuation.SuspendLambdaKt main:5, continuation.SuspendLambdaKt
(a) (a)
main:-1, continuation.SuspendLambdaKt
()
Disconnected from the target VM Disconnected from the target VM
Process finished with exit code 0 Process finished with exit code 0
@@ -30,8 +30,6 @@ CoroutineNameIdState(name=coroutine, id=1, state=RUNNING, dispatcher=BlockingEve
() ()
main:9, continuation.CoroutineSuspendFunKt main:9, continuation.CoroutineSuspendFunKt
(main) (main)
main:-1, continuation.CoroutineSuspendFunKt
()
Creation stack frame Creation stack frame
createCoroutineUnintercepted:116, kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt createCoroutineUnintercepted:116, kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt
() ()
@@ -0,0 +1,46 @@
LineBreakpoint created at coroutineSuspendFun135.kt:29
Run Java
Connected to the target VM
coroutineSuspendFun135.kt:29
Thread stack trace:
c:29, CoroutineSuspendFun135Kt (continuation)
($completion, c, paramB)
b:23, CoroutineSuspendFun135Kt (continuation)
($completion, $continuation, $result, b, paramA)
CoroutineNameIdState(name=coroutine, id=1, state=RUNNING, dispatcher=BlockingEventLoop@305fd85d)
a:16, continuation.CoroutineSuspendFun135Kt
(a)
invokeSuspend:10, continuation.CoroutineSuspendFun135Kt$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:274, kotlinx.coroutines.EventLoopImplBase
(delayed, task, 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:9, continuation.CoroutineSuspendFun135Kt
(main)
Creation stack frame
createCoroutineUnintercepted:116, kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt
()
startCoroutineCancellable:26, kotlinx.coroutines.intrinsics.CancellableKt
()
runBlocking$default:1, kotlinx.coroutines.BuildersKt
()
main:9, continuation.CoroutineSuspendFun135Kt
()
main:-1, continuation.CoroutineSuspendFun135Kt
()
Disconnected from the target VM
Process finished with exit code 0
@@ -118,7 +118,7 @@ private inline fun <T> wrapEvaluateException(block: () -> T): T? {
} }
} }
private inline fun <T> wrapIllegalArgumentException(block: () -> T): T? { public inline fun <T> wrapIllegalArgumentException(block: () -> T): T? {
return try { return try {
block() block()
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {