diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt index adb46ed2f20..81e2ded9822 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineFrameBuilder.kt @@ -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? { + 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, diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt index e328713b90d..093a5b1ac81 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt @@ -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") diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.out b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.out index 7e2c80d3686..0ff47a18838 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.out @@ -7,22 +7,19 @@ Thread stack trace: ($completion, b, bParam) a:21, SuspendFunKt (coroutine1) ($completion, $continuation, $result, a, aParam) - test1:15, SuspendFunKt (coroutine1) - ($completion, $continuation, $result, i, test1) - invoke:9, SuspendFunKt$main$result$1 (coroutine1) +CoroutineNameIdState(name=coroutine, id=-1, state=UNKNOWN, dispatcher=null) + test1:15, coroutine1.SuspendFunKt + (i, test1) + invoke:9, coroutine1.SuspendFunKt$main$result$1 (continuation, p1, this) - invoke:-1, SuspendFunKt$main$result$1 (coroutine1) - (this) - invokeSuspend:121, IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3 (kotlin.coroutines.intrinsics) + invokeSuspend:121, kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3 ($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) - startCoroutine:128, ContinuationKt (kotlin.coroutines) + startCoroutine:128, kotlin.coroutines.ContinuationKt ($this$startCoroutine, completion, receiver) - main:9, SuspendFunKt (coroutine1) + main:9, coroutine1.SuspendFunKt (cnt) - main:-1, SuspendFunKt (coroutine1) - () Disconnected from the target VM Process finished with exit code 0 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out index 0e094402ce5..fc8f0fea5fc 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out @@ -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) hasNext:140, kotlin.sequences.SequenceBuilderIterator (step, this) - hasNext:374, kotlin.sequences.TakeSequence$iterator$1 + hasNext:406, kotlin.sequences.TakeSequence$iterator$1 (this) toCollection:722, kotlin.sequences.SequencesKt___SequencesKt ($this$toCollection, destination) @@ -22,8 +22,6 @@ CoroutineNameIdState(name=coroutine, id=-1, state=UNKNOWN, dispatcher=null) ($this$toList) main:5, continuation.SuspendLambdaKt (a) - main:-1, continuation.SuspendLambdaKt - () Disconnected from the target VM Process finished with exit code 0 diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out index fd6bf5cf55c..e59b6df640e 100644 --- a/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out +++ b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out @@ -30,8 +30,6 @@ CoroutineNameIdState(name=coroutine, id=1, state=RUNNING, dispatcher=BlockingEve () main:9, continuation.CoroutineSuspendFunKt (main) - main:-1, continuation.CoroutineSuspendFunKt - () Creation stack frame createCoroutineUnintercepted:116, kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt () diff --git a/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun135.out b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun135.out new file mode 100644 index 00000000000..c84957cf053 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun135.out @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt index 0a34842b9a6..edaaa25483c 100644 --- a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt +++ b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/safeUtil.kt @@ -118,7 +118,7 @@ private inline fun wrapEvaluateException(block: () -> T): T? { } } -private inline fun wrapIllegalArgumentException(block: () -> T): T? { +public inline fun wrapIllegalArgumentException(block: () -> T): T? { return try { block() } catch (e: IllegalArgumentException) {