[coroutine] running coroutine stack frame merged with coroutine info
This commit is contained in:
committed by
Vladimir Ilmov
parent
f1669e2230
commit
e4d157c126
+7
-18
@@ -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) =
|
||||
|
||||
-1
@@ -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"
|
||||
}
|
||||
|
||||
+5
-5
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+70
-136
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
-4
@@ -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()
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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")
|
||||
|
||||
-87
@@ -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)
|
||||
}
|
||||
+18
-11
@@ -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)
|
||||
|
||||
|
||||
+15
-10
@@ -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
|
||||
|
||||
|
||||
+12
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user