[coroutine] running coroutine stack frame merged with coroutine info

This commit is contained in:
Vladimir Ilmov
2020-01-26 15:21:25 +01:00
committed by Vladimir Ilmov
parent f1669e2230
commit e4d157c126
12 changed files with 190 additions and 278 deletions
@@ -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<CoroutineAsyncStackFrameItem>? =
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<CoroutineStackFrameItem>? =
getAsyncStackTrace(stackFrame, suspendContext as XSuspendContext)
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem>? {
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineStackFrameItem>? {
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) }
return if (stackFrameList == null || stackFrameList.isEmpty()) null else stackFrameList
}
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem> {
val defaultResult = emptyList<CoroutineAsyncStackFrameItem>()
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineStackFrameItem> {
val defaultResult = emptyList<CoroutineStackFrameItem>()
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) =
@@ -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"
}
@@ -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<Project, CoroutineProjectConnectionListener> {
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
}
}
@@ -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<in String>? =
SimpleListCellRenderer.create<String> { label: JBLabel, value: String?, index: Int ->
SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int ->
if (value != null) {
label.text = value
} else if (index >= 0) {
@@ -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<in String>? =
SimpleListCellRenderer.create<String> { label: JBLabel, value: String?, index: Int ->
SimpleListCellRenderer.create { label: JBLabel, value: String?, index: Int ->
if (value != null) {
label.text = value
} else if (index >= 0) {
@@ -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<CoroutineStackFrameItem> {
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
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<StackTraceElement>): 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<StackFrameProxyImpl>): 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<XExecutionStack, SyntheticStackFrame>? {
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()
}
}
}
@@ -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<XNamedValue> = emptyList()
) : CoroutineStackFrameItem(location, spilledVariables) {
fun emptyDescriptor() =
EmptyStackFrameDescriptor(stackTraceElement, frame)
}
class RunningCoroutineStackFrameItem(
val frame: StackFrameProxyImpl,
val stackFrame: XStackFrame,
spilledVariables: List<XNamedValue> = emptyList()
) : CoroutineStackFrameItem(frame.location(), spilledVariables)
class RestoredCoroutineStackFrameItem(
val frame: StackFrameProxyImpl,
location: Location,
spilledVariables: List<XNamedValue>
) : StackFrameItem(location, spilledVariables)
) : CoroutineStackFrameItem(location, spilledVariables) {
fun emptyDescriptor() =
StackFrameDescriptorImpl(frame, MethodsTracker())
}
class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<XNamedValue>) :
CoroutineStackFrameItem(location, spilledVariables)
sealed class CoroutineStackFrameItem(val location: Location, val spilledVariables: List<XNamedValue>) :
StackFrameItem(location, spilledVariables) {
val log by logger
fun uniqueId(): String {
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
location.safeLineNumber() + ":" + location.safeSourceLineNumber()
}
}
@@ -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<CoroutineStackFrameItem>()
// @TODO for refactoring/removal
// @TODO for refactoring/removal along with DumpPanel
val stringStackTrace: String by lazy {
buildString {
appendln("\"$name\", state: $state")
@@ -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<NodeDescriptorImpl>() {
override fun getDisplayKey(): DisplayKey<NodeDescriptorImpl> = 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<CoroutineDescriptorImpl>() {
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<CoroutineDescriptorImpl> = SimpleDisplayKey(infoData.name)
}
@@ -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<CoroutineAsyncStackFrameItem> {
fun getAsyncStackTraceIfAny(): List<CoroutineStackFrameItem> {
val continuation = locateContinuation() ?: return emptyList()
val frames = mutableListOf<CoroutineAsyncStackFrameItem>()
val frames = mutableListOf<CoroutineStackFrameItem>()
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<CoroutineAsyncStackFrameItem>) {
private fun collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<CoroutineStackFrameItem>) {
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<XNamedValue>? {
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)
@@ -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
@@ -89,6 +89,10 @@ fun Field.safeType(): Type? {
return wrapClassNotLoadedException { type() }
}
fun Method.safeSignature() {
wrapIllegalArgumentExceptionException { signature() }
}
private inline fun <T> wrapAbsentInformationException(block: () -> T): T? {
return try {
block()
@@ -101,6 +105,14 @@ private inline fun <T> wrapAbsentInformationException(block: () -> T): T? {
}
}
private inline fun <T> wrapIllegalArgumentExceptionException(block: () -> T): T? {
return try {
block()
} catch (e: IllegalArgumentException) {
null
}
}
private inline fun <T> wrapClassNotLoadedException(block: () -> T): T? {
return try {
block()