diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt index ec3be9085ec..290c09d892b 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineAsyncStackTraceProvider.kt @@ -11,23 +11,22 @@ import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.xdebugger.frame.XSuspendContext import com.sun.jdi.* import org.jetbrains.kotlin.idea.debugger.* -import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineAsyncStackFrameItem +import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.AsyncStackTraceContext import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider { - override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List? = + override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List? = getAsyncStackTrace(stackFrame, suspendContext as XSuspendContext) - - fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List? { + fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List? { val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } return if (stackFrameList == null || stackFrameList.isEmpty()) null else stackFrameList } - fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List { - val defaultResult = emptyList() + fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List { + val defaultResult = emptyList() val location = frameProxy.location() if (!location.isInKotlinSources()) @@ -39,20 +38,10 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider { if (threadReference == null || !threadReference.isSuspended || !canRunEvaluation(suspendContext)) return defaultResult - - val astContext = createAsyncStackTraceContext(frameProxy, suspendContext, method) - return astContext.getAsyncStackTraceIfAny() - } - - private fun createAsyncStackTraceContext( - frameProxy: StackFrameProxyImpl, - suspendContext: XSuspendContext, - method: Method - ): AsyncStackTraceContext { val evaluationContext = EvaluationContextImpl(suspendContext as SuspendContextImpl, frameProxy) val context = ExecutionContext(evaluationContext, frameProxy) - // DebugMetadataKt not found, probably old kotlin-stdlib version - return AsyncStackTraceContext(context, method) + val astContext = AsyncStackTraceContext(context, method) + return astContext.getAsyncStackTraceIfAny() } fun canRunEvaluation(suspendContext: XSuspendContext) = diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineDebuggerContentInfo.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineDebuggerContentInfo.kt index 97085182e0c..41186fc081c 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineDebuggerContentInfo.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineDebuggerContentInfo.kt @@ -9,7 +9,6 @@ import org.jetbrains.annotations.NonNls class CoroutineDebuggerContentInfo { companion object { - val COROUTINE_THREADS_CONTENT = "CoroutineThreadsContent" val XCOROUTINE_THREADS_CONTENT = "XCoroutineThreadsContent" val XCOROUTINE_POPUP_ACTION_GROUP = "Kotlin.XDebugger.Actions" } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineProjectConnectionListener.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineProjectConnectionListener.kt index 0300deb9c9b..8e45623b932 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineProjectConnectionListener.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineProjectConnectionListener.kt @@ -27,7 +27,7 @@ import kotlin.reflect.KProperty class CoroutineProjectConnectionListener(val project: Project) : XDebuggerManagerListener { var connection: MessageBusConnection? = null - val processCounter = AtomicInteger(0) + private val processCounter = AtomicInteger(0) private val log by logger private fun connect() { @@ -40,7 +40,7 @@ class CoroutineProjectConnectionListener(val project: Project) : XDebuggerManage params: JavaParameters?, runnerSettings: RunnerSettings? ) { - if(coroutineDebuggerEnabled()) { + if (coroutineDebuggerEnabled()) { val configurationName = configuration.type.id try { if (!gradleConfiguration(configurationName)) { // gradle test logic in KotlinGradleCoroutineDebugProjectResolver @@ -63,7 +63,7 @@ class CoroutineProjectConnectionListener(val project: Project) : XDebuggerManage } private fun gradleConfiguration(configurationName: String) = - "GradleRunConfiguration".equals(configurationName) || "KotlinGradleRunConfiguration".equals(configurationName) + "GradleRunConfiguration" == configurationName || "KotlinGradleRunConfiguration" == configurationName override fun processStarted(debugProcess: XDebugProcess) = DebuggerInvocationUtil.swingInvokeLater(project) { @@ -102,9 +102,9 @@ val projectListener get() = object : ReadOnlyProperty { lateinit var listenerProject: CoroutineProjectConnectionListener - override fun getValue(project: Project, property: KProperty<*>): CoroutineProjectConnectionListener { + override fun getValue(thisRef: Project, property: KProperty<*>): CoroutineProjectConnectionListener { if (!::listenerProject.isInitialized) - listenerProject = CoroutineProjectConnectionListener(project) + listenerProject = CoroutineProjectConnectionListener(thisRef) return listenerProject } } diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt index 2def0e034ed..0f350516450 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt @@ -9,9 +9,9 @@ import com.intellij.ui.components.JBLabel import com.intellij.ui.SimpleListCellRenderer import javax.swing.ListCellRenderer -class VersionedImplementationProvider() { +class VersionedImplementationProvider { fun comboboxListCellRenderer(): ListCellRenderer? = - SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int -> + SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int -> if (value != null) { label.text = value } else if (index >= 0) { diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.191 b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.191 index 1cf2405cfbf..2a07e978238 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.191 +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/VersionedImplementationProvider.kt.191 @@ -9,9 +9,9 @@ import com.intellij.ui.components.JBLabel import org.jetbrains.kotlin.idea.debugger.coroutine.override.com.intellij.ui.SimpleListCellRenderer import javax.swing.ListCellRenderer -class VersionedImplementationProvider() { +class VersionedImplementationProvider { fun comboboxListCellRenderer(): ListCellRenderer? = - SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int -> + SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int -> if (value != null) { label.text = value } else if (index >= 0) { diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt index ad4944b8d26..d031e1b7d12 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/command/CoroutineBuilder.kt @@ -5,96 +5,118 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.command -import com.intellij.debugger.DebuggerManagerEx -import com.intellij.debugger.engine.* +import com.intellij.debugger.engine.DebugProcess +import com.intellij.debugger.engine.JavaExecutionStack +import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.jdi.ClassesByNameProvider import com.intellij.debugger.jdi.GeneratedLocation import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl -import com.intellij.debugger.memory.utils.StackFrameItem -import com.intellij.debugger.ui.impl.watch.MethodsTracker -import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl -import com.intellij.openapi.project.Project import com.intellij.util.containers.ContainerUtil -import com.intellij.xdebugger.XSourcePosition -import com.intellij.xdebugger.frame.XExecutionStack import com.intellij.xdebugger.frame.XStackFrame import com.intellij.xdebugger.frame.XSuspendContext import com.sun.jdi.* -import org.jetbrains.kotlin.idea.debugger.* import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider -import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData -import org.jetbrains.kotlin.idea.debugger.coroutine.data.SuspendStackFrameDescriptor -import org.jetbrains.kotlin.idea.debugger.coroutine.data.SyntheticStackFrame -import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.AsyncStackTraceContext -import org.jetbrains.kotlin.idea.debugger.coroutine.util.EmptyStackFrameDescriptor -import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger -import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext +import org.jetbrains.kotlin.idea.debugger.coroutine.data.* +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.safeSignature class CoroutineBuilder(val suspendContext: XSuspendContext) { - private val methodsTracker = MethodsTracker() private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider() val debugProcess = (suspendContext as SuspendContextImpl).debugProcess - val virtualMachineProxy = debugProcess.virtualMachineProxy - val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses()) + private val virtualMachineProxy = debugProcess.virtualMachineProxy + private val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses()) companion object { - val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used for creation stacktrace separator in kotlinx.coroutines + const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used for creation stacktrace separator in kotlinx.coroutines } fun build(coroutine: CoroutineInfoData): List { val coroutineStackFrameList = mutableListOf() val firstSuspendedStackFrameProxyImpl = firstSuspendedThreadFrame() val creationFrameSeparatorIndex = findCreationFrameIndex(coroutine.stackTrace) - val positionManager = debugProcess.positionManager if (coroutine.state == CoroutineInfoData.State.RUNNING && coroutine.activeThread is ThreadReference) { val threadReferenceProxyImpl = runningThreadProxy(coroutine.activeThread) val executionStack = JavaExecutionStack(threadReferenceProxyImpl, debugProcess, suspendedSameThread(coroutine.activeThread)) val frames = threadReferenceProxyImpl.forceFrames() - var resumeMethodIndex = findResumeMethodIndex(frames) - for (frameIndex in 0..frames.lastIndex) { - val runningStackFrameProxy = frames[frameIndex] - if (frameIndex == resumeMethodIndex) { - val previousFrame = frames[resumeMethodIndex - 1] - val previousJavaFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true) - val asyncStackTrace = coroutineStackFrameProvider - .getAsyncStackTrace(previousJavaFrame, suspendContext) - asyncStackTrace?.forEach { asyncFrame -> - val xStackFrame = JavaStackFrame(StackFrameDescriptorImpl(previousFrame, methodsTracker), true) - coroutineStackFrameList.add(AsyncCoroutineStackFrameItem(runningStackFrameProxy, asyncFrame, xStackFrame)) + var coroutineStackInserted = false + for (runningStackFrameProxy in frames) { + val jStackFrame = executionStack.createStackFrame(runningStackFrameProxy) + val coroutineStack = coroutineStackFrameProvider.getAsyncStackTraceSafe(runningStackFrameProxy, suspendContext) + if (coroutineStack.isNotEmpty()) { + // clue coroutine stack into the thread's real stack + + val firstMergedFrame = mergeFrameVars(coroutineStack.first(), runningStackFrameProxy, jStackFrame) + coroutineStackFrameList.add(firstMergedFrame) + + for (asyncFrame in coroutineStack.drop(1)) { + coroutineStackFrameList.add( + RestoredCoroutineStackFrameItem( + runningStackFrameProxy, + asyncFrame.location, + asyncFrame.spilledVariables + ) + ) + coroutineStackInserted = true } } else { - val xStackFrame = executionStack.createStackFrame(runningStackFrameProxy) - coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, xStackFrame)) + if (coroutineStackInserted && isInvokeSuspendNegativeLineMethodFrame(runningStackFrameProxy)) { + coroutineStackInserted = false + } else + coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, jStackFrame)) } } - } else if ((coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference) { + } else if ((coroutine.state == CoroutineInfoData.State + .SUSPENDED || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference + ) { // to get frames from CoroutineInfo anyway // the thread is paused on breakpoint - it has at least one frame val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex) for (suspendedFrame in suspendedStackTrace) { - val suspendedXStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, suspendedFrame) - + val location = createLocation(suspendedFrame) coroutineStackFrameList.add( - SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame, coroutine.lastObservedFrameFieldRef) + SuspendCoroutineStackFrameItem( + firstSuspendedStackFrameProxyImpl, + suspendedFrame, + coroutine.lastObservedFrameFieldRef, + location + ) ) } } - val executionStack = JavaExecutionStack(suspendedThreadProxy(), debugProcess, false) - val xStackFrame = executionStack.createStackFrame(firstSuspendedStackFrameProxyImpl) - coroutine.stackTrace.subList(creationFrameSeparatorIndex + 1, coroutine.stackTrace.size).forEach { - var location = createLocation(it) - coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, it, xStackFrame, location)) + val location = createLocation(it) + coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, it, location)) } coroutine.stackFrameList.addAll(coroutineStackFrameList) return coroutineStackFrameList } + /** + * First frames need to be merged as real frame has accurate line number but lacks local variables from coroutine-restored frame. + */ + private fun mergeFrameVars( + restoredFrame: CoroutineStackFrameItem, + runningStackFrameProxy: StackFrameProxyImpl, + jStackFrame: XStackFrame, + ): RunningCoroutineStackFrameItem { + if (restoredFrame.location is GeneratedLocation) { + val restoredMethod = restoredFrame.location.method() + val realMethod = runningStackFrameProxy.location().method() + // if refers to the same method - proceed with merge, otherwise do nothing + if (restoredMethod == realMethod) { + return RunningCoroutineStackFrameItem(runningStackFrameProxy, jStackFrame, restoredFrame.spilledVariables) + } + } + return RunningCoroutineStackFrameItem(runningStackFrameProxy, jStackFrame) + } + private fun suspendedSameThread(activeThread: ThreadReference) = activeThread == suspendedThreadProxy().threadReference @@ -123,20 +145,11 @@ class CoroutineBuilder(val suspendContext: XSuspendContext) { return GeneratedLocation(debugProcess, type, methodName, line) } - fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, location: Location): XStackFrame { - return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!! - } - - fun stackFrame(positionManager: CompoundPositionManager, runningStackFrameProxy: StackFrameProxyImpl, stackTraceElement: StackTraceElement) : XStackFrame { - val location = createLocation(stackTraceElement) - return positionManager.createStackFrame(runningStackFrameProxy, debugProcess, location)!! - } - /** * Tries to find creation frame separator if any, returns last index if none found */ private fun findCreationFrameIndex(frames: List): Int { - var index = frames.indexOfFirst { isCreationSeparatorFrame(it) } + val index = frames.indexOfFirst { isCreationSeparatorFrame(it) } return if (index < 0) frames.lastIndex else @@ -157,88 +170,9 @@ class CoroutineBuilder(val suspendContext: XSuspendContext) { private fun suspendedThreadProxy(): ThreadReferenceProxyImpl = (suspendContext as SuspendContextImpl).thread!! // @TODO hash replace !! - private fun findResumeMethodIndex(frames: List): Int { - for (i: Int in frames.lastIndex downTo 0) - if (isResumeMethodFrame(frames[i])) { - return i - } - return 0 - } - - private fun isResumeMethodFrame(frame: StackFrameProxyImpl) = - frame.safeLocation()?.safeMethod()?.name() == "resumeWith" - - private fun createSyntheticStackFrame( - descriptor: SuspendStackFrameDescriptor, - pos: XSourcePosition, - project: Project - ): Pair? { - val context = DebuggerManagerEx.getInstanceEx(project).context - val suspendContext = context.suspendContext ?: return null - val proxy = suspendContext.thread ?: return null - val executionStack = JavaExecutionStack(proxy, suspendContext.debugProcess, false) - executionStack.initTopFrame() - val evalContext = context.createEvaluationContext() - val frameProxy = evalContext?.frameProxy ?: return null - val execContext = ExecutionContext(evalContext, frameProxy) - val continuation = descriptor.continuation // guaranteed that it is a BaseContinuationImpl - val aMethod = (continuation.type() as ClassType).concreteMethodByName( - "getStackTraceElement", - "()Ljava/lang/StackTraceElement;" - ) - val vars = with(CoroutineAsyncStackTraceProvider()) { - AsyncStackTraceContext( - execContext, - aMethod - ).getSpilledVariables(continuation) - } ?: return null - return executionStack to SyntheticStackFrame(descriptor, vars, pos) - } + private fun isInvokeSuspendNegativeLineMethodFrame(frame: StackFrameProxyImpl) = + frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" && + frame.safeLocation()?.safeMethod()?.safeSignature() ?: "" == "(Ljava/lang/Object;)Ljava/lang/Object;" && + frame.safeLocation()?.safeLineNumber() ?: 0 < 0 } -class CreationCoroutineStackFrameItem( - frame: StackFrameProxyImpl, - val stackTraceElement: StackTraceElement, - stackFrame: XStackFrame, - location: Location -) : CoroutineStackFrameItem(frame, stackFrame, location) { - fun emptyDescriptor() = - EmptyStackFrameDescriptor(stackTraceElement, frame) -} - -class SuspendCoroutineStackFrameItem( - frame: StackFrameProxyImpl, - val stackTraceElement: StackTraceElement, - stackFrame: XStackFrame, - val lastObservedFrameFieldRef: ObjectReference -) : CoroutineStackFrameItem(frame, stackFrame, frame.location()) { - fun emptyDescriptor() = - EmptyStackFrameDescriptor(stackTraceElement, frame) -} - -class AsyncCoroutineStackFrameItem( - frame: StackFrameProxyImpl, - val frameItem: StackFrameItem, - stackFrame: XStackFrame -) : CoroutineStackFrameItem(frame, stackFrame, frame.location()) - -class RunningCoroutineStackFrameItem( - frame: StackFrameProxyImpl, - stackFrame: XStackFrame -) : CoroutineStackFrameItem(frame, stackFrame, frame.location()) - -abstract class CoroutineStackFrameItem(val frame: StackFrameProxyImpl, val stackFrame: XStackFrame, val location: Location) { - val log by logger - - fun sourcePosition() : XSourcePosition? = stackFrame.sourcePosition - - fun uniqueId(): String { - try { - return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" + - location.safeLineNumber() + ":" + location.safeSourceLineNumber() - } catch (e: Exception) { - log.error(e) - return location.method().toString() + ":" + location.lineNumber() - } - } -} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt index 750c3e31b65..5fc1a21da4c 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineAsyncStackFrameItem.kt @@ -5,11 +5,65 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data -import com.intellij.debugger.jdi.GeneratedLocation +import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.memory.utils.StackFrameItem +import com.intellij.debugger.ui.impl.watch.MethodsTracker +import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl import com.intellij.xdebugger.frame.XNamedValue +import com.intellij.xdebugger.frame.XStackFrame +import com.sun.jdi.Location +import com.sun.jdi.ObjectReference +import org.jetbrains.kotlin.idea.debugger.coroutine.util.EmptyStackFrameDescriptor +import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger +import org.jetbrains.kotlin.idea.debugger.safeLineNumber +import org.jetbrains.kotlin.idea.debugger.safeMethod +import org.jetbrains.kotlin.idea.debugger.safeSourceLineNumber +import org.jetbrains.kotlin.idea.debugger.safeSourceName -class CoroutineAsyncStackFrameItem( - val location: GeneratedLocation, +class CreationCoroutineStackFrameItem( + val frame: StackFrameProxyImpl, + val stackTraceElement: StackTraceElement, + location: Location +) : CoroutineStackFrameItem(location, emptyList()) { + fun emptyDescriptor() = + EmptyStackFrameDescriptor(stackTraceElement, frame) +} + +class SuspendCoroutineStackFrameItem( + val frame: StackFrameProxyImpl, + val stackTraceElement: StackTraceElement, + val lastObservedFrameFieldRef: ObjectReference, + location: Location, + spilledVariables: List = emptyList() +) : CoroutineStackFrameItem(location, spilledVariables) { + fun emptyDescriptor() = + EmptyStackFrameDescriptor(stackTraceElement, frame) +} + +class RunningCoroutineStackFrameItem( + val frame: StackFrameProxyImpl, + val stackFrame: XStackFrame, + spilledVariables: List = emptyList() +) : CoroutineStackFrameItem(frame.location(), spilledVariables) + +class RestoredCoroutineStackFrameItem( + val frame: StackFrameProxyImpl, + location: Location, spilledVariables: List -) : StackFrameItem(location, spilledVariables) \ No newline at end of file +) : CoroutineStackFrameItem(location, spilledVariables) { + fun emptyDescriptor() = + StackFrameDescriptorImpl(frame, MethodsTracker()) +} + +class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List) : + CoroutineStackFrameItem(location, spilledVariables) + +sealed class CoroutineStackFrameItem(val location: Location, val spilledVariables: List) : + StackFrameItem(location, spilledVariables) { + val log by logger + + fun uniqueId(): String { + return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" + + location.safeLineNumber() + ":" + location.safeSourceLineNumber() + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt index 3689e201a86..34df3615fc6 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/CoroutineInfoData.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data import com.sun.jdi.* -import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineStackFrameItem /** * Represents state of a coroutine. @@ -23,7 +22,7 @@ data class CoroutineInfoData( ) { var stackFrameList = mutableListOf() - // @TODO for refactoring/removal + // @TODO for refactoring/removal along with DumpPanel val stringStackTrace: String by lazy { buildString { appendln("\"$name\", state: $state") diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/descriptors.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/descriptors.kt deleted file mode 100644 index db70570ea83..00000000000 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/descriptors.kt +++ /dev/null @@ -1,87 +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.data - -import com.intellij.debugger.engine.evaluation.EvaluationContextImpl -import com.intellij.debugger.impl.descriptors.data.DescriptorData -import com.intellij.debugger.impl.descriptors.data.DisplayKey -import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey -import com.intellij.debugger.jdi.StackFrameProxyImpl -import com.intellij.debugger.memory.utils.StackFrameItem -import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl -import com.intellij.openapi.project.Project -import com.sun.jdi.* -import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.LookupContinuation -import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext - -@Deprecated("moved to XCoroutineView") -class CoroutineStackTraceData( - infoData: CoroutineInfoData, - proxy: StackFrameProxyImpl, - evalContext: EvaluationContextImpl, - val frame: StackTraceElement -) : CoroutineStackDescriptorData(infoData, proxy, evalContext) { - - override fun hashCode() = frame.hashCode() - - override fun equals(other: Any?) = - other is CoroutineStackTraceData && frame == other.frame - - override fun createDescriptorImpl(project: Project): NodeDescriptorImpl { - val context = ExecutionContext(evalContext, proxy) - val lookupContinuation = LookupContinuation(context, frame) - - // retrieve continuation only if suspend method - val continuation = lookupContinuation.findContinuation(infoData.lastObservedFrameFieldRef) - - return if (continuation is ObjectReference) - SuspendStackFrameDescriptor(infoData, frame, proxy, continuation) - else - CoroutineCreatedStackFrameDescriptor(frame, proxy) - } -} - -@Deprecated("moved to XCoroutineView") -class CoroutineStackFrameData( - infoData: CoroutineInfoData, - proxy: StackFrameProxyImpl, - evalContext: EvaluationContextImpl, - val frame: StackFrameItem -) : - CoroutineStackDescriptorData(infoData, proxy, evalContext) { - override fun createDescriptorImpl(project: Project): NodeDescriptorImpl = - AsyncStackFrameDescriptor(infoData, frame, proxy) - - override fun hashCode() = frame.hashCode() - - override fun equals(other: Any?) = other is CoroutineStackFrameData && frame == other.frame -} - -@Deprecated("moved to XCoroutineView") -abstract class CoroutineStackDescriptorData( - val infoData: CoroutineInfoData, - val proxy: StackFrameProxyImpl, - val evalContext: EvaluationContextImpl -) : DescriptorData() { - - override fun getDisplayKey(): DisplayKey = SimpleDisplayKey(infoData) -} - -/** - * Describes coroutine itself in the tree (name: STATE), has children if stacktrace is not empty (state = CREATED) - */ -@Deprecated("moved to XCoroutineView") -class CoroutineDescriptorData(private val infoData: CoroutineInfoData) : DescriptorData() { - - override fun createDescriptorImpl(project: Project) = - CoroutineDescriptorImpl(infoData) - - override fun equals(other: Any?) = if (other !is CoroutineDescriptorData) false else infoData.name == other.infoData.name - - override fun hashCode() = infoData.name.hashCode() - - override fun getDisplayKey(): DisplayKey = SimpleDisplayKey(infoData.name) -} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt index 33fbe28382a..3d531f7cb73 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/AsyncStackTraceContext.kt @@ -17,13 +17,15 @@ import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.SUSPEND_LAMBDA_CLASSES -import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineAsyncStackFrameItem +import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem +import org.jetbrains.kotlin.idea.debugger.coroutine.data.DefaultCoroutineStackFrameItem import org.jetbrains.kotlin.idea.debugger.isSubtype import org.jetbrains.kotlin.idea.debugger.safeVisibleVariableByName class AsyncStackTraceContext( val context: ExecutionContext, - val method: Method) { + val method: Method +) { val log by logger val debugMetadataKtType = context.findClassSafe( DEBUG_METADATA_KT @@ -33,9 +35,9 @@ class AsyncStackTraceContext( const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt" } - fun getAsyncStackTraceIfAny() : List { + fun getAsyncStackTraceIfAny(): List { val continuation = locateContinuation() ?: return emptyList() - val frames = mutableListOf() + val frames = mutableListOf() try { collectFramesRecursively(continuation, frames) } catch (e: Exception) { @@ -44,8 +46,8 @@ class AsyncStackTraceContext( return frames } - private fun locateContinuation() : ObjectReference? { - val continuation : ObjectReference? + private fun locateContinuation(): ObjectReference? { + val continuation: ObjectReference? if (isInvokeSuspendMethod(method)) { continuation = context.frameProxy.thisObject() ?: return null if (!isSuspendLambda(continuation.referenceType())) @@ -70,7 +72,7 @@ class AsyncStackTraceContext( private fun isSuspendLambda(referenceType: ReferenceType): Boolean = SUSPEND_LAMBDA_CLASSES.any { referenceType.isSubtype(it) } - private fun collectFramesRecursively(continuation: ObjectReference, consumer: MutableList) { + private fun collectFramesRecursively(continuation: ObjectReference, consumer: MutableList) { val continuationType = continuation.referenceType() as? ClassType ?: return val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return @@ -78,7 +80,7 @@ class AsyncStackTraceContext( location?.let { val spilledVariables = getSpilledVariables(continuation) ?: emptyList() - consumer.add(CoroutineAsyncStackFrameItem(location, spilledVariables)) + consumer.add(DefaultCoroutineStackFrameItem(location, spilledVariables)) } val completionField = baseContinuationSupertype.fieldByName("completion") ?: return @@ -86,11 +88,11 @@ class AsyncStackTraceContext( collectFramesRecursively(completion, consumer) } - private fun createLocation(continuation: ObjectReference) : GeneratedLocation? { + private fun createLocation(continuation: ObjectReference): GeneratedLocation? { val instance = invokeGetStackTraceElement(continuation) ?: return null val className = context.invokeMethodAsString(instance, "getClassName") ?: return null val methodName = context.invokeMethodAsString(instance, "getMethodName") ?: return null - val lineNumber = context.invokeMethodAsInt(instance,"getLineNumber")?.takeIf { + val lineNumber = context.invokeMethodAsInt(instance, "getLineNumber")?.takeIf { it >= 0 } ?: return null val locationClass = context.findClassSafe(className) ?: return null @@ -110,7 +112,12 @@ class AsyncStackTraceContext( fun getSpilledVariables(continuation: ObjectReference): List? { val rawSpilledVariables = - context.invokeMethodAsArray(debugMetadataKtType, "getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;", continuation) ?: return null + context.invokeMethodAsArray( + debugMetadataKtType, + "getSpilledVariableFieldMapping", + "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;", + continuation + ) ?: return null context.keepReference(rawSpilledVariables) diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt index bd0fcfede79..0df77067719 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/XCoroutineView.kt @@ -12,6 +12,7 @@ import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl +import com.intellij.debugger.memory.utils.StackFrameItem import com.intellij.ide.CommonActionsManager import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.ActionManager @@ -47,8 +48,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.VersionedImplementationProvider import org.jetbrains.kotlin.idea.debugger.coroutine.command.* -import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData -import org.jetbrains.kotlin.idea.debugger.coroutine.data.SyntheticStackFrame +import org.jetbrains.kotlin.idea.debugger.coroutine.data.* import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.* import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParams import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParamsProvider @@ -207,7 +207,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) : infoData.stackFrameList.forEach { if (it is CreationCoroutineStackFrameItem) creationStack.add(it) - else + else if (it is CoroutineStackFrameItem) children.add(CoroutineFrameValue(it)) } children.add(CreationFramesContainer(infoData, creationStack)) @@ -281,12 +281,12 @@ class XCoroutineView(val project: Project, val session: XDebugSession) : val threadSuspendContext = session.suspendContext as SuspendContextImpl when (frame) { is RunningCoroutineStackFrameItem -> { - val threadProxy = valueContainer.frame.frame.threadProxy() + val threadProxy = frame.frame.threadProxy() val isCurrentContext = threadSuspendContext.thread == threadProxy createStackAndSetFrame(threadProxy, { frame.stackFrame }, isCurrentContext) } is CreationCoroutineStackFrameItem -> { - val position = getPosition(frame.stackTraceElement) ?: return false + val position = getPosition(frame.stackTraceElement.className, frame.stackTraceElement.lineNumber) ?: return false val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) }) } @@ -295,7 +295,11 @@ class XCoroutineView(val project: Project, val session: XDebugSession) : val executionContext = executionContext(threadSuspendContext, frame.frame) createStackAndSetFrame(threadProxy, { createSyntheticStackFrame(executionContext, frame) }) } - is AsyncCoroutineStackFrameItem -> { + is RestoredCoroutineStackFrameItem -> { + val threadProxy = frame.frame.threadProxy() + val position = getPosition(frame.location.declaringType().name(), frame.location.lineNumber()) ?: return false + createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), frame.spilledVariables, position) }) + } else -> { } @@ -331,15 +335,15 @@ class XCoroutineView(val project: Project, val session: XDebugSession) : JavaExecutionStack(threadReferenceProxy, debugProcess, isCurrentContext) - private fun getPosition(frame: StackTraceElement): XSourcePosition? { + private fun getPosition(className: String, lineNumber: Int): XSourcePosition? { val psiFacade = JavaPsiFacade.getInstance(project) val psiClass = psiFacade.findClass( - frame.className.substringBefore("$"), // find outer class, for which psi exists TODO + className.substringBefore("$"), // find outer class, for which psi exists TODO GlobalSearchScope.everythingScope(project) ) val classFile = psiClass?.containingFile?.virtualFile // to convert to 0-based line number or '-1' to do not move - val lineNumber = if (frame.lineNumber > 0) frame.lineNumber - 1 else return null + val lineNumber = if (lineNumber > 0) lineNumber - 1 else return null return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber) } @@ -352,7 +356,8 @@ class XCoroutineView(val project: Project, val session: XDebugSession) : executionContext: ExecutionContext, frame: SuspendCoroutineStackFrameItem ): SyntheticStackFrame? { - val position = getPosition(frame.stackTraceElement) ?: return null + + val position = applicationThreadExecutor.readAction { getPosition(frame.stackTraceElement.className, frame.stackTraceElement.lineNumber) } ?: return null val lookupContinuation = LookupContinuation(executionContext, frame.stackTraceElement) val continuation = lookupContinuation.findContinuation(frame.lastObservedFrameFieldRef) ?: return null 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 d7c406a2392..431d5f5f03c 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 @@ -89,6 +89,10 @@ fun Field.safeType(): Type? { return wrapClassNotLoadedException { type() } } +fun Method.safeSignature() { + wrapIllegalArgumentExceptionException { signature() } +} + private inline fun wrapAbsentInformationException(block: () -> T): T? { return try { block() @@ -101,6 +105,14 @@ private inline fun wrapAbsentInformationException(block: () -> T): T? { } } +private inline fun wrapIllegalArgumentExceptionException(block: () -> T): T? { + return try { + block() + } catch (e: IllegalArgumentException) { + null + } +} + private inline fun wrapClassNotLoadedException(block: () -> T): T? { return try { block()