(CoroutineDebugger) Alternative CoroutineInfoProvider added
Added way to retrieve coroutine information without an agent in target JVM. 'kotlin.debugger.coroutines.switch' provides two possibilities to test coroutines. Agent way gets activated once kotlinx.coroutines.debug.DebugProbes started with javaagent. Library-less ways use DispatchedContinuation or ChildContinuationImpl classes to retrieve coroutine information and stack traces.
This commit is contained in:
+3
-7
@@ -6,10 +6,10 @@
|
|||||||
package org.jetbrains.kotlin.idea.configuration
|
package org.jetbrains.kotlin.idea.configuration
|
||||||
|
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.util.registry.Registry
|
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.util.Consumer
|
import com.intellij.util.Consumer
|
||||||
import com.intellij.util.SystemProperties
|
import com.intellij.util.SystemProperties
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.standaloneCoroutineDebuggerEnabled
|
||||||
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
|
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
|
||||||
|
|
||||||
class KotlinGradleCoroutineDebugProjectResolver : AbstractProjectResolverExtension() {
|
class KotlinGradleCoroutineDebugProjectResolver : AbstractProjectResolverExtension() {
|
||||||
@@ -17,10 +17,9 @@ class KotlinGradleCoroutineDebugProjectResolver : AbstractProjectResolverExtensi
|
|||||||
|
|
||||||
override fun enhanceTaskProcessing(taskNames: MutableList<String>, jvmParametersSetup: String?, initScriptConsumer: Consumer<String>) {
|
override fun enhanceTaskProcessing(taskNames: MutableList<String>, jvmParametersSetup: String?, initScriptConsumer: Consumer<String>) {
|
||||||
try {
|
try {
|
||||||
if (coroutineDebuggerEnabled())
|
setupCoroutineAgentForJvmForkedTestTasks(initScriptConsumer)
|
||||||
setupCoroutineAgentForJvmForkedTestTasks(initScriptConsumer)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Gradle: not possible to attach coroutine debugger agent. Coroutine debugger disabled.", e)
|
log.error("Gradle: not possible to attach a coroutine debugger agent.", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +36,4 @@ class KotlinGradleCoroutineDebugProjectResolver : AbstractProjectResolverExtensi
|
|||||||
val script = StringUtil.join(lines, SystemProperties.getLineSeparator())
|
val script = StringUtil.join(lines, SystemProperties.getLineSeparator())
|
||||||
initScriptConsumer.consume(script)
|
initScriptConsumer.consume(script)
|
||||||
}
|
}
|
||||||
|
|
||||||
// supposed to be the same as [CoroutineProjectConnectionListener.kt].coroutineDebuggerEnabled
|
|
||||||
private fun coroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines")
|
|
||||||
}
|
}
|
||||||
+1
@@ -23,3 +23,4 @@ coroutine.dump.threads.loading=Loading…
|
|||||||
coroutine.view.title=Coroutines
|
coroutine.view.title=Coroutines
|
||||||
coroutine.view.default.group=Default group
|
coroutine.view.default.group=Default group
|
||||||
coroutine.view.fetching.error=An error occurred on fetching information
|
coroutine.view.fetching.error=An error occurred on fetching information
|
||||||
|
coroutine.view.fetching.not_found=No coroutine information found
|
||||||
|
|||||||
+53
-29
@@ -5,47 +5,71 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine
|
package org.jetbrains.kotlin.idea.debugger.coroutine
|
||||||
|
|
||||||
import com.intellij.debugger.engine.*
|
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.xdebugger.frame.XSuspendContext
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import com.sun.jdi.*
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.*
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.PreCoroutineStackFrameItem
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.AsyncStackTraceContext
|
import org.jetbrains.kotlin.idea.debugger.hopelessAware
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.BaseExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.isInKotlinSources
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
|
||||||
|
|
||||||
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||||
|
|
||||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<CoroutineStackFrameItem>? =
|
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||||
getAsyncStackTrace(stackFrame, suspendContext as XSuspendContext)
|
val stackFrameList = hopelessAware {
|
||||||
|
if (stackFrame is CoroutinePreflightStackFrame)
|
||||||
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineStackFrameItem>? {
|
lookupForAfterPreflight(stackFrame, suspendContext)
|
||||||
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) }
|
else
|
||||||
return if (stackFrameList == null || stackFrameList.isEmpty()) null else stackFrameList
|
null
|
||||||
|
}
|
||||||
|
return if (stackFrameList == null || stackFrameList.isEmpty())
|
||||||
|
null
|
||||||
|
else stackFrameList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineStackFrameItem> {
|
fun lookupForAfterPreflight(
|
||||||
val defaultResult = emptyList<CoroutineStackFrameItem>()
|
stackFrame: CoroutinePreflightStackFrame,
|
||||||
|
suspendContext: SuspendContextImpl
|
||||||
|
): List<CoroutineStackFrameItem>? {
|
||||||
|
val resumeWithFrame = stackFrame.resumeWithFrame
|
||||||
|
|
||||||
|
if (threadAndContextSupportsEvaluation(suspendContext, resumeWithFrame)) {
|
||||||
|
val stackFrames = mutableListOf<CoroutineStackFrameItem>()
|
||||||
|
stackFrames.addAll(
|
||||||
|
stackFrame.restoredStackFrame.drop(1).dropLast(1)
|
||||||
|
) // because first frame has been generated via CoroutinePreflightStackFrame
|
||||||
|
|
||||||
|
val lastRestoredFrame = stackFrame.restoredStackFrame.last()
|
||||||
|
|
||||||
|
stackFrames.addAll(stackFrame.threadPreCoroutineFrames.mapIndexed { index, stackFrameProxyImpl ->
|
||||||
|
if (index == 0)
|
||||||
|
PreCoroutineStackFrameItem(stackFrameProxyImpl, lastRestoredFrame) // get location and variables also from restored part
|
||||||
|
else
|
||||||
|
PreCoroutineStackFrameItem(stackFrameProxyImpl)
|
||||||
|
})
|
||||||
|
return stackFrames
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lookupForResumeContinuation(
|
||||||
|
frameProxy: StackFrameProxyImpl,
|
||||||
|
suspendContext: SuspendContextImpl
|
||||||
|
): List<CoroutineStackFrameItem>? {
|
||||||
val location = frameProxy.location()
|
val location = frameProxy.location()
|
||||||
if (!location.isInKotlinSources())
|
if (!location.isInKotlinSources())
|
||||||
return defaultResult
|
return null
|
||||||
|
|
||||||
val method = location.safeMethod() ?: return defaultResult
|
if (threadAndContextSupportsEvaluation(suspendContext, frameProxy))
|
||||||
val threadReference = frameProxy.threadProxy().threadReference
|
return ContinuationHolder.lookupForResumeMethodContinuation(suspendContext, frameProxy)
|
||||||
|
?.getAsyncStackTraceIfAny()?.stackFrameItems
|
||||||
if (threadReference == null || !threadReference.isSuspended || !canRunEvaluation(suspendContext))
|
return null
|
||||||
return defaultResult
|
|
||||||
|
|
||||||
val context = DefaultExecutionContext(suspendContext as SuspendContextImpl, frameProxy)
|
|
||||||
val astContext = AsyncStackTraceContext(context, method)
|
|
||||||
return astContext.getAsyncStackTraceIfAny()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canRunEvaluation(suspendContext: XSuspendContext) =
|
private fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl) =
|
||||||
(suspendContext as SuspendContextImpl).debugProcess.canRunEvaluation
|
suspendContext.supportsEvaluation() && frameProxy.threadProxy().supportsEvaluation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import com.intellij.execution.ExecutionException;
|
||||||
|
import com.intellij.execution.RunConfigurationExtension;
|
||||||
|
import com.intellij.execution.configurations.JavaParameters;
|
||||||
|
import com.intellij.execution.configurations.RunConfigurationBase;
|
||||||
|
import com.intellij.execution.configurations.RunnerSettings;
|
||||||
|
import com.intellij.openapi.components.ServiceManager;
|
||||||
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
|
||||||
|
public class CoroutineDebugConfigurationExtension extends RunConfigurationExtension {
|
||||||
|
private static final Logger log = Logger.getInstance(CoroutineDebugConfigurationExtension.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends RunConfigurationBase> void updateJavaParameters(
|
||||||
|
T configuration, JavaParameters params, RunnerSettings runnerSettings
|
||||||
|
) throws ExecutionException {
|
||||||
|
if (configuration != null) {
|
||||||
|
Project project = configuration.getProject();
|
||||||
|
DebuggerListener listener = ServiceManager.getService(project, DebuggerListener.class);
|
||||||
|
if (listener != null)
|
||||||
|
listener.registerDebuggerConnection(configuration, params, runnerSettings);
|
||||||
|
else
|
||||||
|
log.error("DebuggerListener service is not found in project " + project.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicableFor(RunConfigurationBase<?> configuration) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
-30
@@ -1,30 +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
|
|
||||||
|
|
||||||
import com.intellij.execution.RunConfigurationExtension
|
|
||||||
import com.intellij.execution.configurations.DebuggingRunnerData
|
|
||||||
import com.intellij.execution.configurations.JavaParameters
|
|
||||||
import com.intellij.execution.configurations.RunConfigurationBase
|
|
||||||
import com.intellij.execution.configurations.RunnerSettings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs coroutines debug agent and coroutines tab if `kotlinx-coroutines-debug` dependency is found
|
|
||||||
*/
|
|
||||||
@Suppress("IncompatibleAPI")
|
|
||||||
class CoroutineDebugConfigurationExtension : RunConfigurationExtension() {
|
|
||||||
|
|
||||||
override fun isApplicableFor(configuration: RunConfigurationBase<*>) = coroutineDebuggerEnabled()
|
|
||||||
|
|
||||||
override fun <T : RunConfigurationBase<*>?> updateJavaParameters(
|
|
||||||
configuration: T,
|
|
||||||
params: JavaParameters,
|
|
||||||
runnerSettings: RunnerSettings?
|
|
||||||
) {
|
|
||||||
if (runnerSettings is DebuggingRunnerData && configuration is RunConfigurationBase<*>) {
|
|
||||||
configuration.project.coroutineConnectionListener.configurationStarting(configuration, params, runnerSettings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-30
@@ -1,30 +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
|
|
||||||
|
|
||||||
import com.intellij.execution.RunConfigurationExtension
|
|
||||||
import com.intellij.execution.configurations.DebuggingRunnerData
|
|
||||||
import com.intellij.execution.configurations.JavaParameters
|
|
||||||
import com.intellij.execution.configurations.RunConfigurationBase
|
|
||||||
import com.intellij.execution.configurations.RunnerSettings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs coroutines debug agent and coroutines tab if `kotlinx-coroutines-debug` dependency is found
|
|
||||||
*/
|
|
||||||
@Suppress("IncompatibleAPI")
|
|
||||||
class CoroutineDebugConfigurationExtension : RunConfigurationExtension() {
|
|
||||||
|
|
||||||
override fun isApplicableFor(configuration: RunConfigurationBase<*>) = coroutineDebuggerEnabled()
|
|
||||||
|
|
||||||
override fun <T : RunConfigurationBase<*>?> updateJavaParameters(
|
|
||||||
configuration: T,
|
|
||||||
params: JavaParameters?,
|
|
||||||
runnerSettings: RunnerSettings?
|
|
||||||
) {
|
|
||||||
if (runnerSettings is DebuggingRunnerData && configuration is RunConfigurationBase<*>) {
|
|
||||||
configuration.project.coroutineConnectionListener.configurationStarting(configuration, params, runnerSettings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-7
@@ -13,10 +13,3 @@ class CoroutineDebuggerContentInfo {
|
|||||||
val XCOROUTINE_POPUP_ACTION_GROUP = "Kotlin.XDebugger.Actions"
|
val XCOROUTINE_POPUP_ACTION_GROUP = "Kotlin.XDebugger.Actions"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoroutineDebuggerActions {
|
|
||||||
companion object {
|
|
||||||
@NonNls
|
|
||||||
val COROUTINE_PANEL_POPUP: String = "Debugger.CoroutinesPanelPopup"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-117
@@ -1,117 +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
|
|
||||||
|
|
||||||
import com.intellij.debugger.DebuggerInvocationUtil
|
|
||||||
import com.intellij.debugger.engine.JavaDebugProcess
|
|
||||||
import com.intellij.execution.configurations.JavaParameters
|
|
||||||
import com.intellij.execution.configurations.RunConfigurationBase
|
|
||||||
import com.intellij.execution.configurations.RunnerSettings
|
|
||||||
import com.intellij.execution.ui.RunnerLayoutUi
|
|
||||||
import com.intellij.execution.ui.layout.PlaceInGrid
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.util.registry.Registry
|
|
||||||
import com.intellij.ui.content.Content
|
|
||||||
import com.intellij.util.messages.MessageBusConnection
|
|
||||||
import com.intellij.xdebugger.*
|
|
||||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.*
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.view.XCoroutineView
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
|
|
||||||
class CoroutineProjectConnectionListener(val project: Project) : XDebuggerManagerListener {
|
|
||||||
var connection: MessageBusConnection? = null
|
|
||||||
private val processCounter = AtomicInteger(0)
|
|
||||||
private val log by logger
|
|
||||||
|
|
||||||
private fun connect() {
|
|
||||||
connection = project.messageBus.connect()
|
|
||||||
connection?.subscribe(XDebuggerManager.TOPIC, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun configurationStarting(
|
|
||||||
configuration: RunConfigurationBase<*>,
|
|
||||||
params: JavaParameters?,
|
|
||||||
runnerSettings: RunnerSettings?
|
|
||||||
) {
|
|
||||||
if (coroutineDebuggerEnabled()) {
|
|
||||||
val configurationName = configuration.type.id
|
|
||||||
try {
|
|
||||||
if (!gradleConfiguration(configurationName)) { // gradle test logic in KotlinGradleCoroutineDebugProjectResolver
|
|
||||||
val kotlinxCoroutinesClassPathLib =
|
|
||||||
params?.classPath?.pathList?.first { it.contains("kotlinx-coroutines-debug") }
|
|
||||||
initializeCoroutineAgent(params!!, kotlinxCoroutinesClassPathLib)
|
|
||||||
}
|
|
||||||
starting()
|
|
||||||
} catch (e: NoSuchElementException) {
|
|
||||||
log.warn("'kotlinx-coroutines-debug' not found in classpath. Coroutine debugger disabled.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun starting() {
|
|
||||||
if (processCounter.compareAndSet(0, 1))
|
|
||||||
connect()
|
|
||||||
else
|
|
||||||
processCounter.incrementAndGet()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun gradleConfiguration(configurationName: String) =
|
|
||||||
"GradleRunConfiguration" == configurationName || "KotlinGradleRunConfiguration" == configurationName
|
|
||||||
|
|
||||||
override fun processStarted(debugProcess: XDebugProcess) =
|
|
||||||
DebuggerInvocationUtil.swingInvokeLater(project) {
|
|
||||||
if (debugProcess is JavaDebugProcess) {
|
|
||||||
registerXCoroutinesPanel(debugProcess.session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun processStopped(debugProcess: XDebugProcess) {
|
|
||||||
if (processCounter.compareAndSet(1, 0)) {
|
|
||||||
connection?.disconnect()
|
|
||||||
connection = null
|
|
||||||
} else
|
|
||||||
processCounter.decrementAndGet()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun registerXCoroutinesPanel(session: XDebugSession) {
|
|
||||||
val ui = session.ui ?: return
|
|
||||||
val xCoroutineThreadView = XCoroutineView(project, session as XDebugSessionImpl)
|
|
||||||
val framesContent: Content = createContent(ui, xCoroutineThreadView)
|
|
||||||
framesContent.isCloseable = false
|
|
||||||
ui.addContent(framesContent, 0, PlaceInGrid.right, false)
|
|
||||||
session.addSessionListener(xCoroutineThreadView.debugSessionListener(session))
|
|
||||||
session.rebuildViews()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createContent(ui: RunnerLayoutUi, createContentParamProvider: CreateContentParamsProvider): Content {
|
|
||||||
val param = createContentParamProvider.createContentParams()
|
|
||||||
return ui.createContent(param.id, param.component, param.displayName, param.icon, param.parentComponent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val Project.coroutineConnectionListener by projectListener
|
|
||||||
|
|
||||||
val projectListener
|
|
||||||
get() = object : ReadOnlyProperty<Project, CoroutineProjectConnectionListener> {
|
|
||||||
lateinit var listenerProject: CoroutineProjectConnectionListener
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Project, property: KProperty<*>): CoroutineProjectConnectionListener {
|
|
||||||
if (!::listenerProject.isInitialized)
|
|
||||||
listenerProject = CoroutineProjectConnectionListener(thisRef)
|
|
||||||
return listenerProject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun coroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines")
|
|
||||||
|
|
||||||
internal fun initializeCoroutineAgent(params: JavaParameters, it: String?) {
|
|
||||||
params.vmParametersList?.add("-javaagent:$it")
|
|
||||||
params.vmParametersList?.add("-ea")
|
|
||||||
}
|
|
||||||
+5
-1
@@ -14,8 +14,12 @@ import com.intellij.xdebugger.frame.XStackFrame
|
|||||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||||
import com.sun.jdi.Location
|
import com.sun.jdi.Location
|
||||||
import org.jetbrains.kotlin.idea.debugger.StackFrameInterceptor
|
import org.jetbrains.kotlin.idea.debugger.StackFrameInterceptor
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||||
|
|
||||||
class CoroutineStackFrameInterceptor(val project: Project) : StackFrameInterceptor {
|
class CoroutineStackFrameInterceptor(val project: Project) : StackFrameInterceptor {
|
||||||
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? =
|
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? =
|
||||||
null
|
if (AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl))
|
||||||
|
ContinuationHolder.coroutineExitFrame(frame, debugProcess.debuggerContext.suspendContext as SuspendContextImpl)
|
||||||
|
else
|
||||||
|
null
|
||||||
}
|
}
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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
|
||||||
|
|
||||||
|
import com.intellij.debugger.DebuggerInvocationUtil
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.engine.JavaDebugProcess
|
||||||
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
|
import com.intellij.debugger.impl.PrioritizedTask
|
||||||
|
import com.intellij.execution.configurations.DebuggingRunnerData
|
||||||
|
import com.intellij.execution.configurations.JavaParameters
|
||||||
|
import com.intellij.execution.configurations.RunConfigurationBase
|
||||||
|
import com.intellij.execution.ui.RunnerLayoutUi
|
||||||
|
import com.intellij.execution.ui.layout.PlaceInGrid
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Disposer
|
||||||
|
import com.intellij.openapi.util.registry.Registry
|
||||||
|
import com.intellij.ui.content.Content
|
||||||
|
import com.intellij.util.messages.MessageBusConnection
|
||||||
|
import com.intellij.xdebugger.XDebugProcess
|
||||||
|
import com.intellij.xdebugger.XDebugSession
|
||||||
|
import com.intellij.xdebugger.XDebuggerManager
|
||||||
|
import com.intellij.xdebugger.XDebuggerManagerListener
|
||||||
|
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ManagerThreadExecutor
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParamsProvider
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.view.XCoroutineView
|
||||||
|
|
||||||
|
class DebuggerConnection(
|
||||||
|
val project: Project,
|
||||||
|
val configuration: RunConfigurationBase<*>,
|
||||||
|
val params: JavaParameters?,
|
||||||
|
val runnerSettings: DebuggingRunnerData?
|
||||||
|
) : XDebuggerManagerListener {
|
||||||
|
var disposable: Disposable? = null
|
||||||
|
var connection: MessageBusConnection? = null
|
||||||
|
private val log by logger
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (params is JavaParameters) {
|
||||||
|
// gradle related logic in KotlinGradleCoroutineDebugProjectResolver
|
||||||
|
val kotlinxCoroutinesClassPathLib =
|
||||||
|
params.classPath?.pathList?.firstOrNull { it.contains("kotlinx-coroutines-debug") }
|
||||||
|
if (kotlinxCoroutinesClassPathLib is String)
|
||||||
|
initializeCoroutineAgent(params, kotlinxCoroutinesClassPathLib)
|
||||||
|
else
|
||||||
|
log.warn("'kotlinx-coroutines-debug' not found in classpath.")
|
||||||
|
}
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeCoroutineAgent(params: JavaParameters, it: String?) {
|
||||||
|
params.vmParametersList?.add("-javaagent:$it")
|
||||||
|
params.vmParametersList?.add("-ea")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connect() {
|
||||||
|
connection = project.messageBus.connect()
|
||||||
|
connection?.subscribe(XDebuggerManager.TOPIC, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processStarted(debugProcess: XDebugProcess) =
|
||||||
|
DebuggerInvocationUtil.swingInvokeLater(project) {
|
||||||
|
if (debugProcess is JavaDebugProcess) {
|
||||||
|
disposable = registerXCoroutinesPanel(debugProcess.session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processStopped(debugProcess: XDebugProcess) {
|
||||||
|
val rootDisposable = disposable
|
||||||
|
if (rootDisposable is Disposable && debugProcess is JavaDebugProcess && debugProcess.session.suspendContext is SuspendContextImpl) {
|
||||||
|
ManagerThreadExecutor(debugProcess).on(debugProcess.session.suspendContext).schedule {
|
||||||
|
Disposer.dispose(rootDisposable)
|
||||||
|
disposable = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connection?.disconnect()
|
||||||
|
connection = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerXCoroutinesPanel(session: XDebugSession): Disposable? {
|
||||||
|
val ui = session.ui ?: return null
|
||||||
|
val xCoroutineThreadView = XCoroutineView(project, session as XDebugSessionImpl)
|
||||||
|
val framesContent: Content = createContent(ui, xCoroutineThreadView)
|
||||||
|
framesContent.isCloseable = false
|
||||||
|
ui.addContent(framesContent, 0, PlaceInGrid.right, false)
|
||||||
|
session.addSessionListener(xCoroutineThreadView.debugSessionListener(session))
|
||||||
|
session.rebuildViews()
|
||||||
|
return xCoroutineThreadView
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createContent(ui: RunnerLayoutUi, createContentParamProvider: CreateContentParamsProvider): Content {
|
||||||
|
val param = createContentParamProvider.createContentParams()
|
||||||
|
return ui.createContent(param.id, param.component, param.displayName, param.icon, param.parentComponent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun standaloneCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines.standalone")
|
||||||
|
|
||||||
|
fun coroutineDebuggerTraceEnabled() = Registry.`is`("kotlin.debugger.coroutines.trace")
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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
|
||||||
|
|
||||||
|
import com.intellij.execution.configurations.DebuggingRunnerData
|
||||||
|
import com.intellij.execution.configurations.JavaParameters
|
||||||
|
import com.intellij.execution.configurations.RunConfigurationBase
|
||||||
|
import com.intellij.execution.configurations.RunnerSettings
|
||||||
|
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.xdebugger.XDebuggerManagerListener
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
|
||||||
|
interface DebuggerListener : XDebuggerManagerListener {
|
||||||
|
fun registerDebuggerConnection(
|
||||||
|
configuration: RunConfigurationBase<*>,
|
||||||
|
params: JavaParameters?,
|
||||||
|
runnerSettings: RunnerSettings?
|
||||||
|
): DebuggerConnection?
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoroutineDebuggerListener(val project: Project) : DebuggerListener {
|
||||||
|
val log by logger
|
||||||
|
|
||||||
|
override fun registerDebuggerConnection(
|
||||||
|
configuration: RunConfigurationBase<*>,
|
||||||
|
params: JavaParameters?,
|
||||||
|
runnerSettings: RunnerSettings?
|
||||||
|
): DebuggerConnection? {
|
||||||
|
val isExternalSystemRunConfiguration = configuration is ExternalSystemRunConfiguration
|
||||||
|
val isGradleConfiguration = gradleConfiguration(configuration.type.id)
|
||||||
|
|
||||||
|
if (runnerSettings == null || isExternalSystemRunConfiguration || isGradleConfiguration) {
|
||||||
|
log.warn("Coroutine debugger in standalone mode for ${configuration.name} ${configuration.javaClass} / ${params?.javaClass} / ${runnerSettings?.javaClass} (if enabled)")
|
||||||
|
} else if (runnerSettings is DebuggingRunnerData?)
|
||||||
|
return DebuggerConnection(project, configuration, params, runnerSettings)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun gradleConfiguration(configurationName: String) =
|
||||||
|
"GradleRunConfiguration" == configurationName || "KotlinGradleRunConfiguration" == configurationName
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine
|
package org.jetbrains.kotlin.idea.debugger.coroutine
|
||||||
|
|
||||||
import com.intellij.ui.components.JBLabel
|
|
||||||
import com.intellij.ui.SimpleListCellRenderer
|
import com.intellij.ui.SimpleListCellRenderer
|
||||||
|
import com.intellij.ui.components.JBLabel
|
||||||
import javax.swing.ListCellRenderer
|
import javax.swing.ListCellRenderer
|
||||||
|
|
||||||
class VersionedImplementationProvider {
|
class VersionedImplementationProvider {
|
||||||
|
|||||||
+37
-131
@@ -13,162 +13,68 @@ import com.intellij.debugger.jdi.GeneratedLocation
|
|||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||||
import com.intellij.util.containers.ContainerUtil
|
import com.intellij.util.containers.ContainerUtil
|
||||||
import com.intellij.xdebugger.frame.XStackFrame
|
|
||||||
import com.intellij.xdebugger.frame.XSuspendContext
|
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.isPreFlight
|
||||||
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
|
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
|
||||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||||
|
|
||||||
|
|
||||||
class CoroutineBuilder(val suspendContext: XSuspendContext) {
|
class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
|
||||||
private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
|
private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
|
||||||
val debugProcess = (suspendContext as SuspendContextImpl).debugProcess
|
val debugProcess = suspendContext.debugProcess
|
||||||
private val virtualMachineProxy = debugProcess.virtualMachineProxy
|
private val virtualMachineProxy = debugProcess.virtualMachineProxy
|
||||||
private val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses())
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const 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 as creation stacktrace separator in kotlinx.coroutines
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(coroutine: CoroutineInfoData): List<CoroutineStackFrameItem> {
|
fun build(coroutine: CoroutineInfoData): List<CoroutineStackFrameItem> {
|
||||||
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
||||||
val firstSuspendedStackFrameProxyImpl = firstSuspendedThreadFrame()
|
|
||||||
val creationFrameSeparatorIndex = findCreationFrameIndex(coroutine.stackTrace)
|
|
||||||
|
|
||||||
if (coroutine.state == CoroutineInfoData.State.RUNNING && coroutine.activeThread is ThreadReference) {
|
if (coroutine.isRunning() && coroutine.activeThread is ThreadReference) {
|
||||||
val threadReferenceProxyImpl = runningThreadProxy(coroutine.activeThread)
|
val threadReferenceProxyImpl = ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, coroutine.activeThread)
|
||||||
val executionStack = JavaExecutionStack(threadReferenceProxyImpl, debugProcess, suspendedSameThread(coroutine.activeThread))
|
|
||||||
|
|
||||||
val frames = threadReferenceProxyImpl.forceFrames()
|
val realFrames = threadReferenceProxyImpl.forceFrames()
|
||||||
var coroutineStackInserted = false
|
var coroutineStackInserted = false
|
||||||
for (runningStackFrameProxy in frames) {
|
var preflightFound = false
|
||||||
val jStackFrame = executionStack.createStackFrame(runningStackFrameProxy)
|
for (runningStackFrameProxy in realFrames) {
|
||||||
val coroutineStack = coroutineStackFrameProvider.getAsyncStackTraceSafe(runningStackFrameProxy, suspendContext)
|
if (runningStackFrameProxy.location().isPreFlight()) {
|
||||||
if (coroutineStack.isNotEmpty()) {
|
preflightFound = true
|
||||||
// clue coroutine stack into the thread's real stack
|
continue
|
||||||
|
|
||||||
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 {
|
|
||||||
if (coroutineStackInserted && isInvokeSuspendNegativeLineMethodFrame(runningStackFrameProxy)) {
|
|
||||||
coroutineStackInserted = false
|
|
||||||
} else
|
|
||||||
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, jStackFrame))
|
|
||||||
}
|
}
|
||||||
}
|
if (preflightFound) {
|
||||||
} else if ((coroutine.state == CoroutineInfoData.State
|
val coroutineStack = coroutineStackFrameProvider.lookupForResumeContinuation(runningStackFrameProxy, suspendContext)
|
||||||
.SUSPENDED || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference
|
if (coroutineStack?.isNotEmpty() == true) {
|
||||||
) {
|
// clue coroutine stack into the thread's real stack
|
||||||
// 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 location = createLocation(suspendedFrame)
|
|
||||||
coroutineStackFrameList.add(
|
|
||||||
SuspendCoroutineStackFrameItem(
|
|
||||||
firstSuspendedStackFrameProxyImpl,
|
|
||||||
suspendedFrame,
|
|
||||||
coroutine.lastObservedFrameFieldRef,
|
|
||||||
location
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coroutine.stackTrace.subList(creationFrameSeparatorIndex + 1, coroutine.stackTrace.size).forEach {
|
for (asyncFrame in coroutineStack) {
|
||||||
val location = createLocation(it)
|
coroutineStackFrameList.add(
|
||||||
coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, it, location))
|
RestoredCoroutineStackFrameItem(
|
||||||
}
|
runningStackFrameProxy,
|
||||||
|
asyncFrame.location,
|
||||||
|
asyncFrame.spilledVariables
|
||||||
|
)
|
||||||
|
)
|
||||||
|
coroutineStackInserted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preflightFound = false
|
||||||
|
}
|
||||||
|
if (!(coroutineStackInserted && isInvokeSuspendNegativeLineMethodFrame(runningStackFrameProxy)))
|
||||||
|
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy))
|
||||||
|
coroutineStackInserted = false
|
||||||
|
}
|
||||||
|
} else if ((coroutine.isSuspended() || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference)
|
||||||
|
coroutineStackFrameList.addAll(coroutine.stackTrace)
|
||||||
|
|
||||||
|
coroutineStackFrameList.addAll(coroutine.creationStackTrace)
|
||||||
coroutine.stackFrameList.addAll(coroutineStackFrameList)
|
coroutine.stackFrameList.addAll(coroutineStackFrameList)
|
||||||
return 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
|
|
||||||
|
|
||||||
private fun createLocation(stackTraceElement: StackTraceElement): Location = findLocation(
|
|
||||||
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
|
|
||||||
stackTraceElement.methodName,
|
|
||||||
stackTraceElement.lineNumber
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun findLocation(
|
|
||||||
type: ReferenceType?,
|
|
||||||
methodName: String,
|
|
||||||
line: Int
|
|
||||||
): Location {
|
|
||||||
if (type != null && line >= 0) {
|
|
||||||
try {
|
|
||||||
val location = type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line).stream()
|
|
||||||
.filter { l: Location -> l.method().name() == methodName }
|
|
||||||
.findFirst().orElse(null)
|
|
||||||
if (location != null) {
|
|
||||||
return location
|
|
||||||
}
|
|
||||||
} catch (ignored: AbsentInformationException) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GeneratedLocation(debugProcess, type, methodName, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to find creation frame separator if any, returns last index if none found
|
|
||||||
*/
|
|
||||||
private fun findCreationFrameIndex(frames: List<StackTraceElement>): Int {
|
|
||||||
val index = frames.indexOfFirst { isCreationSeparatorFrame(it) }
|
|
||||||
return if (index < 0)
|
|
||||||
frames.lastIndex
|
|
||||||
else
|
|
||||||
index
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isCreationSeparatorFrame(it: StackTraceElement) =
|
|
||||||
it.className.startsWith(CREATION_STACK_TRACE_SEPARATOR)
|
|
||||||
|
|
||||||
private fun firstSuspendedThreadFrame(): StackFrameProxyImpl =
|
|
||||||
suspendedThreadProxy().forceFrames().first()
|
|
||||||
|
|
||||||
// retrieves currently suspended but active and executing coroutine thread proxy
|
|
||||||
fun runningThreadProxy(threadReference: ThreadReference) =
|
|
||||||
ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, threadReference)
|
|
||||||
|
|
||||||
// retrieves current suspended thread proxy
|
|
||||||
private fun suspendedThreadProxy(): ThreadReferenceProxyImpl =
|
|
||||||
(suspendContext as SuspendContextImpl).thread!! // @TODO hash replace !!
|
|
||||||
|
|
||||||
private fun isInvokeSuspendNegativeLineMethodFrame(frame: StackFrameProxyImpl) =
|
private fun isInvokeSuspendNegativeLineMethodFrame(frame: StackFrameProxyImpl) =
|
||||||
frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" &&
|
frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" &&
|
||||||
frame.safeLocation()?.safeMethod()?.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" &&
|
frame.safeLocation()?.safeMethod()?.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" &&
|
||||||
|
|||||||
+4
-5
@@ -25,7 +25,6 @@ import com.intellij.util.text.DateFormatUtil
|
|||||||
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.view.CoroutineDumpPanel
|
import org.jetbrains.kotlin.idea.debugger.coroutine.view.CoroutineDumpPanel
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.coroutineDebuggerEnabled
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy
|
||||||
|
|
||||||
@@ -43,14 +42,14 @@ class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
|
|||||||
val states = CoroutineDebugProbesProxy(context.suspendContext ?: return)
|
val states = CoroutineDebugProbesProxy(context.suspendContext ?: return)
|
||||||
.dumpCoroutines()
|
.dumpCoroutines()
|
||||||
if (states.isOk()) {
|
if (states.isOk()) {
|
||||||
val message = KotlinDebuggerCoroutinesBundle.message("coroutine.dump.failed")
|
|
||||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message,MessageType.ERROR).notify(project)
|
|
||||||
} else {
|
|
||||||
val f = fun() {
|
val f = fun() {
|
||||||
val ui = session.xDebugSession?.ui ?: return
|
val ui = session.xDebugSession?.ui ?: return
|
||||||
addCoroutineDump(project, states.cache, ui, session.searchScope)
|
addCoroutineDump(project, states.cache, ui, session.searchScope)
|
||||||
}
|
}
|
||||||
ApplicationManager.getApplication().invokeLater(f, ModalityState.NON_MODAL)
|
ApplicationManager.getApplication().invokeLater(f, ModalityState.NON_MODAL)
|
||||||
|
} else {
|
||||||
|
val message = KotlinDebuggerCoroutinesBundle.message("coroutine.dump.failed")
|
||||||
|
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message,MessageType.ERROR).notify(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -93,7 +92,7 @@ class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
val debuggerSession = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession
|
val debuggerSession = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession
|
||||||
presentation.isEnabled = debuggerSession != null && debuggerSession.isAttached && coroutineDebuggerEnabled()
|
presentation.isEnabled = debuggerSession != null && debuggerSession.isAttached
|
||||||
presentation.isVisible = presentation.isEnabled
|
presentation.isVisible = presentation.isEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.DebuggerContext
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
|
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.sun.jdi.Value
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||||
|
|
||||||
|
class ContinuationValueDescriptorImpl(
|
||||||
|
project: Project,
|
||||||
|
val continuation: ContinuationHolder,
|
||||||
|
val fieldName: String,
|
||||||
|
val variableName: String
|
||||||
|
) : ValueDescriptorImpl(project) {
|
||||||
|
override fun calcValueName() = variableName
|
||||||
|
|
||||||
|
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
|
||||||
|
val field = continuation.referenceType()?.fieldByName(fieldName) ?: return null
|
||||||
|
return continuation.field(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||||
|
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||||
|
}
|
||||||
+86
-9
@@ -1,48 +1,55 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
* Copyright 2010-2020 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.
|
* 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
|
package org.jetbrains.kotlin.idea.debugger.coroutine.data
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||||
|
import com.intellij.ui.ColoredTextContainer
|
||||||
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
|
import com.intellij.xdebugger.frame.XCompositeNode
|
||||||
import com.intellij.xdebugger.frame.XNamedValue
|
import com.intellij.xdebugger.frame.XNamedValue
|
||||||
import com.intellij.xdebugger.frame.XStackFrame
|
import com.intellij.xdebugger.frame.XStackFrame
|
||||||
import com.sun.jdi.Location
|
import com.sun.jdi.Location
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.ObjectReference
|
||||||
import org.jetbrains.kotlin.idea.debugger.*
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.EmptyStackFrameDescriptor
|
import org.jetbrains.kotlin.idea.debugger.coroutine.coroutineDebuggerTraceEnabled
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
|
||||||
class CreationCoroutineStackFrameItem(
|
class CreationCoroutineStackFrameItem(
|
||||||
val frame: StackFrameProxyImpl,
|
|
||||||
val stackTraceElement: StackTraceElement,
|
val stackTraceElement: StackTraceElement,
|
||||||
location: Location
|
location: Location
|
||||||
) : CoroutineStackFrameItem(location, emptyList()) {
|
) : CoroutineStackFrameItem(location, emptyList()) {
|
||||||
fun emptyDescriptor() =
|
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspended frames in Suspended coroutine
|
||||||
|
*/
|
||||||
class SuspendCoroutineStackFrameItem(
|
class SuspendCoroutineStackFrameItem(
|
||||||
val frame: StackFrameProxyImpl,
|
|
||||||
val stackTraceElement: StackTraceElement,
|
val stackTraceElement: StackTraceElement,
|
||||||
val lastObservedFrameFieldRef: ObjectReference,
|
|
||||||
location: Location,
|
location: Location,
|
||||||
spilledVariables: List<XNamedValue> = emptyList()
|
spilledVariables: List<XNamedValue> = emptyList()
|
||||||
) : CoroutineStackFrameItem(location, spilledVariables) {
|
) : CoroutineStackFrameItem(location, spilledVariables) {
|
||||||
fun emptyDescriptor() =
|
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
class RunningCoroutineStackFrameItem(
|
class RunningCoroutineStackFrameItem(
|
||||||
val frame: StackFrameProxyImpl,
|
val frame: StackFrameProxyImpl,
|
||||||
val stackFrame: XStackFrame,
|
// val stackFrame: XStackFrame,
|
||||||
spilledVariables: List<XNamedValue> = emptyList()
|
spilledVariables: List<XNamedValue> = emptyList()
|
||||||
) : CoroutineStackFrameItem(frame.location(), spilledVariables)
|
) : CoroutineStackFrameItem(frame.location(), spilledVariables)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restored frame in Running coroutine, attaching to running thread
|
||||||
|
*/
|
||||||
class RestoredCoroutineStackFrameItem(
|
class RestoredCoroutineStackFrameItem(
|
||||||
val frame: StackFrameProxyImpl,
|
val frame: StackFrameProxyImpl,
|
||||||
location: Location,
|
location: Location,
|
||||||
@@ -52,8 +59,71 @@ class RestoredCoroutineStackFrameItem(
|
|||||||
StackFrameDescriptorImpl(frame, MethodsTracker())
|
StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restored from memory dump
|
||||||
|
*/
|
||||||
class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<XNamedValue>) :
|
class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<XNamedValue>) :
|
||||||
CoroutineStackFrameItem(location, spilledVariables)
|
CoroutineStackFrameItem(location, spilledVariables) {
|
||||||
|
|
||||||
|
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||||
|
StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original frame appeared before resumeWith call.
|
||||||
|
*
|
||||||
|
* Sequence is the following
|
||||||
|
*
|
||||||
|
* - KotlinStackFrame
|
||||||
|
* - invokeSuspend(KotlinStackFrame) -|
|
||||||
|
* | replaced with CoroutinePreflightStackFrame
|
||||||
|
* - resumeWith(KotlinStackFrame) ----|
|
||||||
|
* - PreCoroutineStackFrameItem part of CoroutinePreflightStackFrame
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class PreCoroutineStackFrameItem(val frame: StackFrameProxyImpl, location: Location, variables: List<XNamedValue> = emptyList()) :
|
||||||
|
CoroutineStackFrameItem(location, variables) {
|
||||||
|
constructor(frame: StackFrameProxyImpl, variables: List<XNamedValue> = emptyList()) : this(frame, frame.location(), variables)
|
||||||
|
|
||||||
|
constructor(frame: StackFrameProxyImpl, restoredCoroutineStackFrameItem: CoroutineStackFrameItem) : this(
|
||||||
|
frame,
|
||||||
|
restoredCoroutineStackFrameItem.location,
|
||||||
|
restoredCoroutineStackFrameItem.spilledVariables
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
|
||||||
|
return PreCoroutineStackFrame(frame, debugProcess, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can act as a joint frame, take variables form restored frame and information from the original one.
|
||||||
|
*/
|
||||||
|
class PreCoroutineStackFrame(val frame: StackFrameProxyImpl, val debugProcess: DebugProcessImpl, item: StackFrameItem) :
|
||||||
|
CoroutineStackFrame(debugProcess, item) {
|
||||||
|
override fun computeChildren(node: XCompositeNode) {
|
||||||
|
debugProcess.invokeInManagerThread {
|
||||||
|
debugProcess.getPositionManager().createStackFrame(frame, debugProcess, frame.location())
|
||||||
|
?.computeChildren(node) // hack but works
|
||||||
|
}
|
||||||
|
super.computeChildren(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class CoroutineStackFrame(debugProcess: DebugProcessImpl, val item: StackFrameItem) :
|
||||||
|
StackFrameItem.CapturedStackFrame(debugProcess, item) {
|
||||||
|
override fun customizePresentation(component: ColoredTextContainer) {
|
||||||
|
if (coroutineDebuggerTraceEnabled())
|
||||||
|
component.append("${item.javaClass.simpleName} / ${this.javaClass.simpleName}", SimpleTextAttributes.GRAYED_ATTRIBUTES)
|
||||||
|
super.customizePresentation(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCaptionAboveOf() = "CoroutineExit"
|
||||||
|
|
||||||
|
override fun hasSeparatorAbove(): Boolean =
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sealed class CoroutineStackFrameItem(val location: Location, val spilledVariables: List<XNamedValue>) :
|
sealed class CoroutineStackFrameItem(val location: Location, val spilledVariables: List<XNamedValue>) :
|
||||||
StackFrameItem(location, spilledVariables) {
|
StackFrameItem(location, spilledVariables) {
|
||||||
@@ -63,4 +133,11 @@ sealed class CoroutineStackFrameItem(val location: Location, val spilledVariable
|
|||||||
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
|
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
|
||||||
location.safeLineNumber() + ":" + location.safeKotlinPreferredLineNumber()
|
location.safeLineNumber() + ":" + location.safeKotlinPreferredLineNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
|
||||||
|
return CoroutineStackFrame(debugProcess, this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
||||||
|
StackFrameDescriptorImpl(proxy, MethodsTracker())
|
||||||
|
|||||||
+2
-1
@@ -14,9 +14,10 @@ class CoroutineInfoCache(
|
|||||||
state = CacheState.OK
|
state = CacheState.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fail() {
|
fun fail(): CoroutineInfoCache {
|
||||||
cache.clear()
|
cache.clear()
|
||||||
state = CacheState.FAIL
|
state = CacheState.FAIL
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOk(): Boolean {
|
fun isOk(): Boolean {
|
||||||
|
|||||||
+31
-13
@@ -7,17 +7,16 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
|
|||||||
|
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.ObjectReference
|
||||||
import com.sun.jdi.ThreadReference
|
import com.sun.jdi.ThreadReference
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineHolder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents state of a coroutine.
|
* Represents state of a coroutine.
|
||||||
* @see `kotlinx.coroutines.debug.CoroutineInfo`
|
* @see `kotlinx.coroutines.debug.CoroutineInfo`
|
||||||
*/
|
*/
|
||||||
data class CoroutineInfoData(
|
data class CoroutineInfoData(
|
||||||
val name: String,
|
val key: CoroutineNameIdState,
|
||||||
val state: State,
|
val stackTrace: List<CoroutineStackFrameItem>,
|
||||||
|
val creationStackTrace: List<CreationCoroutineStackFrameItem>,
|
||||||
val stackTrace: List<StackTraceElement>,
|
|
||||||
// links to jdi.* references
|
|
||||||
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
||||||
val lastObservedFrameFieldRef: ObjectReference?
|
val lastObservedFrameFieldRef: ObjectReference?
|
||||||
) {
|
) {
|
||||||
@@ -26,22 +25,41 @@ data class CoroutineInfoData(
|
|||||||
// @TODO for refactoring/removal along with DumpPanel
|
// @TODO for refactoring/removal along with DumpPanel
|
||||||
val stringStackTrace: String by lazy {
|
val stringStackTrace: String by lazy {
|
||||||
buildString {
|
buildString {
|
||||||
appendln("\"$name\", state: $state")
|
appendln("\"${key.name}\", state: ${key.state}")
|
||||||
stackTrace.forEach {
|
stackTrace.forEach {
|
||||||
appendln("\t$it")
|
appendln("\t$it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSuspended() = state == State.SUSPENDED
|
fun isSuspended() = key.state == State.SUSPENDED
|
||||||
|
|
||||||
fun isCreated() = state == State.CREATED
|
fun isCreated() = key.state == State.CREATED
|
||||||
|
|
||||||
fun isEmptyStackTrace() = stackTrace.isEmpty()
|
fun isEmptyStack() = stackTrace.isEmpty()
|
||||||
|
|
||||||
enum class State {
|
fun isRunning() = key.state == State.RUNNING
|
||||||
RUNNING,
|
|
||||||
SUSPENDED,
|
companion object {
|
||||||
CREATED
|
fun suspendedCoroutineInfoData(
|
||||||
|
holder: CoroutineHolder,
|
||||||
|
lastObservedFrameFieldRef: ObjectReference
|
||||||
|
): CoroutineInfoData? {
|
||||||
|
return CoroutineInfoData(holder.info, holder.stackFrameItems, emptyList(), null, lastObservedFrameFieldRef)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class CoroutineNameIdState(val name: String, val id: String, val state: State)
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
RUNNING,
|
||||||
|
SUSPENDED,
|
||||||
|
CREATED,
|
||||||
|
UNKNOWN,
|
||||||
|
SUSPENDED_COMPLETING,
|
||||||
|
SUSPENDED_CANCELLING,
|
||||||
|
CANCELLED,
|
||||||
|
COMPLETED,
|
||||||
|
NEW
|
||||||
|
}
|
||||||
+4
-4
@@ -24,18 +24,18 @@ import javax.swing.Icon
|
|||||||
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
|
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
|
||||||
lateinit var icon: Icon
|
lateinit var icon: Icon
|
||||||
|
|
||||||
override fun getName() = infoData.name
|
override fun getName() = infoData.key.name
|
||||||
|
|
||||||
@Throws(EvaluateException::class)
|
@Throws(EvaluateException::class)
|
||||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
|
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
|
||||||
val thread = infoData.activeThread
|
val thread = infoData.activeThread
|
||||||
val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: ""
|
val name = thread?.name()?.substringBefore(" @${infoData.key.name}") ?: ""
|
||||||
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
|
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
|
||||||
val threadName = if (name.isNotEmpty()) " on thread \"$name\":$threadState" else ""
|
val threadName = if (name.isNotEmpty()) " on thread \"$name\":$threadState" else ""
|
||||||
return "${infoData.name}: ${infoData.state} $threadName"
|
return "${infoData.key.name}: ${infoData.key.state} $threadName"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isExpandable() = infoData.state != CoroutineInfoData.State.CREATED
|
override fun isExpandable() = infoData.key.state != State.CREATED
|
||||||
|
|
||||||
private fun calcIcon() = when {
|
private fun calcIcon() = when {
|
||||||
infoData.isSuspended() -> AllIcons.Debugger.ThreadSuspended
|
infoData.isSuspended() -> AllIcons.Debugger.ThreadSuspended
|
||||||
|
|||||||
-174
@@ -1,174 +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.proxy
|
|
||||||
|
|
||||||
import com.intellij.debugger.DebuggerContext
|
|
||||||
import com.intellij.debugger.engine.JavaValue
|
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
|
||||||
import com.intellij.debugger.jdi.GeneratedLocation
|
|
||||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
|
||||||
import com.intellij.xdebugger.frame.XNamedValue
|
|
||||||
import com.sun.jdi.*
|
|
||||||
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.CoroutineStackFrameItem
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.DefaultCoroutineStackFrameItem
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.isSubtype
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.safeVisibleVariableByName
|
|
||||||
|
|
||||||
class AsyncStackTraceContext(
|
|
||||||
val context: DefaultExecutionContext,
|
|
||||||
val method: Method
|
|
||||||
) {
|
|
||||||
val log by logger
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAsyncStackTraceIfAny(): List<CoroutineStackFrameItem> {
|
|
||||||
val continuation = locateContinuation() ?: return emptyList()
|
|
||||||
val frames = mutableListOf<CoroutineStackFrameItem>()
|
|
||||||
try {
|
|
||||||
collectFramesRecursively(continuation, frames)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log.error("Error while looking for variables.", e)
|
|
||||||
}
|
|
||||||
return frames
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun locateContinuation(): ObjectReference? {
|
|
||||||
val continuation: ObjectReference?
|
|
||||||
if (isInvokeSuspendMethod(method)) {
|
|
||||||
continuation = context.frameProxy?.thisObject() ?: return null
|
|
||||||
if (!isSuspendLambda(continuation.referenceType()))
|
|
||||||
return null
|
|
||||||
} else if (isContinuationProvider(method)) {
|
|
||||||
val continuationVariable = context.frameProxy?.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
|
||||||
continuation = context.frameProxy?.getValue(continuationVariable) as? ObjectReference ?: return null
|
|
||||||
context.keepReference(continuation)
|
|
||||||
} else {
|
|
||||||
continuation = null
|
|
||||||
}
|
|
||||||
return continuation
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isInvokeSuspendMethod(method: Method): Boolean =
|
|
||||||
method.name() == "invokeSuspend" && method.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;"
|
|
||||||
|
|
||||||
private fun isContinuationProvider(method: Method): Boolean =
|
|
||||||
"Lkotlin/coroutines/Continuation;)" in method.signature()
|
|
||||||
|
|
||||||
private fun isSuspendLambda(referenceType: ReferenceType): Boolean =
|
|
||||||
SUSPEND_LAMBDA_CLASSES.any { referenceType.isSubtype(it) }
|
|
||||||
|
|
||||||
private fun collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<CoroutineStackFrameItem>) {
|
|
||||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
|
||||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
|
||||||
|
|
||||||
val debugMetadataKtType = debugMetadataKtType()
|
|
||||||
if (debugMetadataKtType is ClassType) {
|
|
||||||
val location = createLocation(continuation, debugMetadataKtType)
|
|
||||||
|
|
||||||
location?.let {
|
|
||||||
val spilledVariables = getSpilledVariables(continuation, debugMetadataKtType) ?: emptyList()
|
|
||||||
consumer.add(DefaultCoroutineStackFrameItem(location, spilledVariables))
|
|
||||||
}
|
|
||||||
|
|
||||||
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
|
||||||
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
|
||||||
collectFramesRecursively(completion, consumer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun debugMetadataKtType(): ClassType? {
|
|
||||||
val debugMetadataKtType = context.findClassSafe(DEBUG_METADATA_KT)
|
|
||||||
if (debugMetadataKtType != null) {
|
|
||||||
return debugMetadataKtType
|
|
||||||
} else {
|
|
||||||
log.warn("Continuation information found but no $DEBUG_METADATA_KT class exists. Please check kotlin-stdlib version.")
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createLocation(continuation: ObjectReference, debugMetadataKtType: ClassType): GeneratedLocation? {
|
|
||||||
val instance = invokeGetStackTraceElement(continuation, debugMetadataKtType) ?: 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 {
|
|
||||||
it >= 0
|
|
||||||
} ?: return null
|
|
||||||
val locationClass = context.findClassSafe(className) ?: return null
|
|
||||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun invokeGetStackTraceElement(continuation: ObjectReference, debugMetadataKtType: ClassType): ObjectReference? {
|
|
||||||
val stackTraceElement =
|
|
||||||
context.invokeMethodAsObject(debugMetadataKtType, "getStackTraceElement", continuation) ?: return null
|
|
||||||
|
|
||||||
stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name } ?: return null
|
|
||||||
|
|
||||||
context.keepReference(stackTraceElement)
|
|
||||||
return stackTraceElement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
|
||||||
debugMetadataKtType()?.let {
|
|
||||||
return getSpilledVariables(continuation, it)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSpilledVariables(continuation: ObjectReference, debugMetadataKtType: ClassType): List<XNamedValue>? {
|
|
||||||
val rawSpilledVariables =
|
|
||||||
context.invokeMethodAsArray(
|
|
||||||
debugMetadataKtType,
|
|
||||||
"getSpilledVariableFieldMapping",
|
|
||||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;",
|
|
||||||
continuation
|
|
||||||
) ?: return null
|
|
||||||
|
|
||||||
context.keepReference(rawSpilledVariables)
|
|
||||||
|
|
||||||
val length = rawSpilledVariables.length() / 2
|
|
||||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
|
||||||
|
|
||||||
for (index in 0 until length) {
|
|
||||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
|
||||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
|
||||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
|
||||||
|
|
||||||
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
|
||||||
override fun calcValueName() = variableName
|
|
||||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
|
||||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
|
||||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
spilledVariables += JavaValue.create(
|
|
||||||
null,
|
|
||||||
valueDescriptor,
|
|
||||||
context.evaluationContext,
|
|
||||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return spilledVariables
|
|
||||||
}
|
|
||||||
|
|
||||||
private tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
|
||||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
|
||||||
return type
|
|
||||||
}
|
|
||||||
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+239
@@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.JavaValue
|
||||||
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
|
import com.intellij.debugger.jdi.GeneratedLocation
|
||||||
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
|
import com.intellij.xdebugger.frame.XNamedValue
|
||||||
|
import com.intellij.xdebugger.frame.XStackFrame
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.ContinuationValueDescriptorImpl
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.DefaultCoroutineStackFrameItem
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.standaloneCoroutineDebuggerEnabled
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
||||||
|
|
||||||
|
data class ContinuationHolder(val continuation: ObjectReference, val context: DefaultExecutionContext) {
|
||||||
|
val log by logger
|
||||||
|
|
||||||
|
fun getAsyncStackTraceIfAny(): CoroutineHolder? {
|
||||||
|
try {
|
||||||
|
return collectFrames()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Error while looking for variables.", e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectFrames(): CoroutineHolder? {
|
||||||
|
val consumer = mutableListOf<CoroutineStackFrameItem>()
|
||||||
|
var completion = this
|
||||||
|
val debugMetadataKtType = debugMetadataKtType() ?: return null
|
||||||
|
while (completion.isBaseContinuationImpl()) {
|
||||||
|
val coroutineStackFrame = context.debugProcess.invokeInManagerThread {
|
||||||
|
createLocation(completion, debugMetadataKtType)
|
||||||
|
}
|
||||||
|
if (coroutineStackFrame != null) {
|
||||||
|
consumer.add(coroutineStackFrame)
|
||||||
|
}
|
||||||
|
completion = completion.findCompletion() ?: break
|
||||||
|
}
|
||||||
|
if (completion.value().type().isAbstractCoroutine())
|
||||||
|
return CoroutineHolder.lookup(completion.value(), context, consumer)
|
||||||
|
else {
|
||||||
|
log.warn("AbstractCoroutine not found, ${completion.value().type()} is not subtype of AbstractCoroutine as expected.")
|
||||||
|
return CoroutineHolder.lookup(null, context, consumer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createLocation(continuation: ContinuationHolder, debugMetadataKtType: ClassType): DefaultCoroutineStackFrameItem? {
|
||||||
|
val instance = invokeGetStackTraceElement(continuation, debugMetadataKtType) ?: 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 {
|
||||||
|
it >= 0
|
||||||
|
} ?: return null // skip invokeSuspend:-1
|
||||||
|
val locationClass = context.findClassSafe(className) ?: return null
|
||||||
|
val generatedLocation = GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||||
|
val spilledVariables = getSpilledVariables(continuation, debugMetadataKtType) ?: emptyList()
|
||||||
|
return DefaultCoroutineStackFrameItem(generatedLocation, spilledVariables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invokeGetStackTraceElement(continuation: ContinuationHolder, debugMetadataKtType: ClassType): ObjectReference? {
|
||||||
|
val stackTraceElement =
|
||||||
|
context.invokeMethodAsObject(debugMetadataKtType, "getStackTraceElement", continuation.value()) ?: return null
|
||||||
|
|
||||||
|
stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name } ?: return null
|
||||||
|
context.keepReference(stackTraceElement)
|
||||||
|
return stackTraceElement
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSpilledVariables(): List<XNamedValue>? {
|
||||||
|
debugMetadataKtType()?.let {
|
||||||
|
return getSpilledVariables(this, it)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSpilledVariables(continuation: ContinuationHolder, debugMetadataKtType: ClassType): List<XNamedValue>? {
|
||||||
|
val variables: List<JavaValue> = context.debugProcess.invokeInManagerThread {
|
||||||
|
FieldVariable.extractFromContinuation(context, continuation.value(), debugMetadataKtType).map {
|
||||||
|
val valueDescriptor = ContinuationValueDescriptorImpl(
|
||||||
|
context.project,
|
||||||
|
continuation,
|
||||||
|
it.fieldName,
|
||||||
|
it.variableName
|
||||||
|
)
|
||||||
|
JavaValue.create(
|
||||||
|
null,
|
||||||
|
valueDescriptor,
|
||||||
|
context.evaluationContext,
|
||||||
|
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: emptyList()
|
||||||
|
return variables
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun debugMetadataKtType(): ClassType? {
|
||||||
|
val debugMetadataKtType = context.findCoroutineMetadataType()
|
||||||
|
if (debugMetadataKtType == null)
|
||||||
|
log.warn("Continuation information found but no 'kotlin.coroutines.jvm.internal.DebugMetadataKt' class exists. Please check kotlin-stdlib version.")
|
||||||
|
return debugMetadataKtType
|
||||||
|
}
|
||||||
|
|
||||||
|
fun referenceType(): ClassType? =
|
||||||
|
continuation.referenceType() as? ClassType
|
||||||
|
|
||||||
|
fun value() =
|
||||||
|
continuation
|
||||||
|
|
||||||
|
fun field(field: Field): Value? =
|
||||||
|
continuation.getValue(field)
|
||||||
|
|
||||||
|
fun findCompletion(): ContinuationHolder? {
|
||||||
|
val type = continuation.type()
|
||||||
|
if (type is ClassType && type.isBaseContinuationImpl()) {
|
||||||
|
val completionField = type.completionField() ?: return null
|
||||||
|
return ContinuationHolder(continuation.getValue(completionField) as? ObjectReference ?: return null, context)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isBaseContinuationImpl() =
|
||||||
|
continuation.type().isBaseContinuationImpl()
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val log by logger
|
||||||
|
|
||||||
|
fun lookupForResumeMethodContinuation(
|
||||||
|
suspendContext: SuspendContextImpl,
|
||||||
|
frame: StackFrameProxyImpl
|
||||||
|
): ContinuationHolder? {
|
||||||
|
if (frame.location().isPreExitFrame()) {
|
||||||
|
val context = suspendContext.executionContext() ?: return null
|
||||||
|
var continuation = frame.completionVariableValue() ?: return null
|
||||||
|
context.keepReference(continuation)
|
||||||
|
return ContinuationHolder(continuation, context)
|
||||||
|
} else
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun coroutineExitFrame(
|
||||||
|
frame: StackFrameProxyImpl,
|
||||||
|
suspendContext: SuspendContextImpl
|
||||||
|
): XStackFrame? {
|
||||||
|
return suspendContext.invokeInManagerThread {
|
||||||
|
if (frame.location().isPreFlight()) {
|
||||||
|
if(standaloneCoroutineDebuggerEnabled())
|
||||||
|
log.trace("Entry frame found: ${formatLocation(frame.location())}")
|
||||||
|
constructPreFlightFrame(frame, suspendContext)
|
||||||
|
} else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun constructPreFlightFrame(
|
||||||
|
invokeSuspendFrame: StackFrameProxyImpl,
|
||||||
|
suspendContext: SuspendContextImpl
|
||||||
|
): CoroutinePreflightStackFrame? {
|
||||||
|
var frames = invokeSuspendFrame.threadProxy().frames()
|
||||||
|
val indexOfCurrentFrame = frames.indexOf(invokeSuspendFrame)
|
||||||
|
val indexofgetcoroutineSuspended = findGetCoroutineSuspended(frames)
|
||||||
|
// @TODO if found - skip this thread stack
|
||||||
|
if (indexOfCurrentFrame > 0 && frames.size > indexOfCurrentFrame && indexofgetcoroutineSuspended < 0) {
|
||||||
|
val resumeWithFrame = frames[indexOfCurrentFrame + 1] ?: return null
|
||||||
|
val ch = lookupForResumeMethodContinuation(suspendContext, resumeWithFrame) ?: return null
|
||||||
|
|
||||||
|
val coroutineStackTrace = ch.getAsyncStackTraceIfAny() ?: return null
|
||||||
|
return CoroutinePreflightStackFrame.preflight(
|
||||||
|
invokeSuspendFrame,
|
||||||
|
resumeWithFrame,
|
||||||
|
coroutineStackTrace.stackFrameItems,
|
||||||
|
frames.drop(indexOfCurrentFrame)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatLocation(location: Location): String {
|
||||||
|
return "${location.method().name()}:${location.lineNumber()}, ${location.method().declaringType()} in ${location.sourceName()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find continuation for the [frame]
|
||||||
|
* Gets current CoroutineInfo.lastObservedFrame and finds next frames in it until null or needed stackTraceElement is found
|
||||||
|
* @return null if matching continuation is not found or is not BaseContinuationImpl
|
||||||
|
*/
|
||||||
|
fun lookup(
|
||||||
|
context: SuspendContextImpl,
|
||||||
|
initialContinuation: ObjectReference?,
|
||||||
|
// frame: StackTraceElement,
|
||||||
|
// threadProxy: ThreadReferenceProxyImpl
|
||||||
|
): ContinuationHolder? {
|
||||||
|
var continuation = initialContinuation ?: return null
|
||||||
|
// val classLine = ClassNameLineNumber(frame.className, frame.lineNumber)
|
||||||
|
val executionContext = context.executionContext() ?: return null
|
||||||
|
|
||||||
|
do {
|
||||||
|
// val position = getClassAndLineNumber(executionContext, continuation)
|
||||||
|
// while continuation is BaseContinuationImpl and it's frame equals to the current
|
||||||
|
continuation = getNextFrame(executionContext, continuation) ?: return null
|
||||||
|
} while (continuation.type().isBaseContinuationImpl() /* && position != classLine */)
|
||||||
|
|
||||||
|
return if (continuation.type().isBaseContinuationImpl())
|
||||||
|
ContinuationHolder(continuation, executionContext)
|
||||||
|
else
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ClassNameLineNumber(val className: String?, val lineNumber: Int?)
|
||||||
|
//
|
||||||
|
// private fun getClassAndLineNumber(context: ExecutionContext, continuation: ObjectReference): ClassNameLineNumber {
|
||||||
|
// val objectReference = findStackTraceElement(context, continuation) ?: return ClassNameLineNumber(null, null)
|
||||||
|
// val classStackTraceElement = context.findClass("java.lang.StackTraceElement") as ClassType
|
||||||
|
// val getClassName = classStackTraceElement.concreteMethodByName("getClassName", "()Ljava/lang/String;")
|
||||||
|
// val getLineNumber = classStackTraceElement.concreteMethodByName("getLineNumber", "()I")
|
||||||
|
// val className = (context.invokeMethod(objectReference, getClassName, emptyList()) as StringReference).value()
|
||||||
|
// val lineNumber = (context.invokeMethod(objectReference, getLineNumber, emptyList()) as IntegerValue).value()
|
||||||
|
// return ClassNameLineNumber(className, lineNumber)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private fun findStackTraceElement(context: ExecutionContext, continuation: ObjectReference): ObjectReference? {
|
||||||
|
// val classType = continuation.type() as ClassType
|
||||||
|
// val methodGetStackTraceElement = classType.concreteMethodByName("getStackTraceElement", "()Ljava/lang/StackTraceElement;")
|
||||||
|
// return context.invokeMethod(continuation, methodGetStackTraceElement, emptyList()) as? ObjectReference
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+9
-209
@@ -4,48 +4,29 @@
|
|||||||
*/
|
*/
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
|
||||||
import com.intellij.xdebugger.frame.XSuspendContext
|
|
||||||
import com.sun.jdi.*
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineBuilder
|
import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineBuilder
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoCache
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoCache
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
|
||||||
|
|
||||||
class CoroutineDebugProbesProxy(val suspendContext: XSuspendContext) {
|
class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||||
private val log by logger
|
private val log by logger
|
||||||
|
|
||||||
private var executionContext: DefaultExecutionContext = executionContext()
|
|
||||||
// might want to use inner class but also having to monitor order of fields
|
|
||||||
private var refs: ProcessReferences = ProcessReferences(executionContext)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val DEBUG_PACKAGE = "kotlinx.coroutines.debug"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
@Suppress("unused")
|
|
||||||
fun install() =
|
|
||||||
executionContext.invokeMethodAsVoid(refs.instance, "install")
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
@Suppress("unused")
|
|
||||||
fun uninstall() =
|
|
||||||
executionContext.invokeMethodAsVoid(refs.instance, "uninstall")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes DebugProbes from debugged process's classpath and returns states of coroutines
|
* Invokes DebugProbes from debugged process's classpath and returns states of coroutines
|
||||||
* Should be invoked on debugger manager thread
|
* Should be invoked on debugger manager thread
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun dumpCoroutines() : CoroutineInfoCache {
|
fun dumpCoroutines(): CoroutineInfoCache {
|
||||||
|
DebuggerManagerThreadImpl.assertIsManagerThread()
|
||||||
val coroutineInfoCache = CoroutineInfoCache()
|
val coroutineInfoCache = CoroutineInfoCache()
|
||||||
try {
|
try {
|
||||||
val infoList = dump()
|
val executionContext = suspendContext.executionContext() ?: return coroutineInfoCache.fail()
|
||||||
|
val libraryAgentProxy = findProvider(executionContext)
|
||||||
|
val infoList = libraryAgentProxy.dumpCoroutinesInfo()
|
||||||
coroutineInfoCache.ok(infoList)
|
coroutineInfoCache.ok(infoList)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error("Exception is thrown by calling dumpCoroutines.", e)
|
log.error("Exception is thrown by calling dumpCoroutines.", e)
|
||||||
@@ -54,189 +35,8 @@ class CoroutineDebugProbesProxy(val suspendContext: XSuspendContext) {
|
|||||||
return coroutineInfoCache
|
return coroutineInfoCache
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dump(): List<CoroutineInfoData> {
|
private fun findProvider(executionContext: DefaultExecutionContext) =
|
||||||
val coroutinesInfo = dumpCoroutinesInfo() ?: return emptyList()
|
CoroutineLibraryAgentProxy.instance(executionContext) ?: CoroutineNoLibraryProxy(executionContext)
|
||||||
|
|
||||||
executionContext.keepReference(coroutinesInfo)
|
|
||||||
val size = sizeOf(coroutinesInfo)
|
|
||||||
|
|
||||||
return MutableList(size) {
|
|
||||||
val elem = getElementFromList(coroutinesInfo, it)
|
|
||||||
fetchCoroutineState(elem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dumpCoroutinesInfo() =
|
|
||||||
executionContext.invokeMethodAsObject(refs.instance, refs.dumpMethod)
|
|
||||||
|
|
||||||
fun frameBuilder() = CoroutineBuilder(suspendContext)
|
fun frameBuilder() = CoroutineBuilder(suspendContext)
|
||||||
|
|
||||||
private fun getElementFromList(instance: ObjectReference, num: Int) =
|
|
||||||
executionContext.invokeMethod(
|
|
||||||
instance, refs.getRef,
|
|
||||||
listOf(executionContext.vm.virtualMachine.mirrorOf(num))
|
|
||||||
) as ObjectReference
|
|
||||||
|
|
||||||
private fun fetchCoroutineState(instance: ObjectReference) : CoroutineInfoData {
|
|
||||||
val name = getName(instance)
|
|
||||||
val state = getState(instance)
|
|
||||||
val thread = getLastObservedThread(instance, refs.lastObservedThreadFieldRef)
|
|
||||||
val lastObservedFrameFieldRef = instance.getValue(refs.lastObservedFrameFieldRef) as? ObjectReference
|
|
||||||
val stackTrace = getStackTrace(instance)
|
|
||||||
return CoroutineInfoData(
|
|
||||||
name,
|
|
||||||
CoroutineInfoData.State.valueOf(state),
|
|
||||||
stackTrace,
|
|
||||||
thread,
|
|
||||||
lastObservedFrameFieldRef
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getName(
|
|
||||||
info: ObjectReference // CoroutineInfo instance
|
|
||||||
): String {
|
|
||||||
// equals to `coroutineInfo.context.get(CoroutineName).name`
|
|
||||||
val coroutineContextInst = executionContext.invokeMethod(
|
|
||||||
info,
|
|
||||||
refs.getContextRef,
|
|
||||||
emptyList()
|
|
||||||
) as? ObjectReference ?: throw IllegalArgumentException("Coroutine context must not be null")
|
|
||||||
val coroutineName = executionContext.invokeMethod(
|
|
||||||
coroutineContextInst,
|
|
||||||
refs.getContextElement, listOf(refs.keyFieldValueRef)
|
|
||||||
) as? ObjectReference
|
|
||||||
// If the coroutine doesn't have a given name, CoroutineContext.get(CoroutineName) returns null
|
|
||||||
val name = if (coroutineName != null) (executionContext.invokeMethod(
|
|
||||||
coroutineName,
|
|
||||||
refs.getNameRef,
|
|
||||||
emptyList()
|
|
||||||
) as StringReference).value() else "coroutine"
|
|
||||||
val id = (info.getValue(refs.sequenceNumberFieldRef) as LongValue).value()
|
|
||||||
return "$name#$id"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getState(
|
|
||||||
info: ObjectReference // CoroutineInfo instance
|
|
||||||
): String {
|
|
||||||
// equals to `stringState = coroutineInfo.state.toString()`
|
|
||||||
val state = executionContext.invokeMethod(info, refs.getStateRef, emptyList()) as ObjectReference
|
|
||||||
return (executionContext.invokeMethod(state, refs.toString, emptyList()) as StringReference).value()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLastObservedThread(
|
|
||||||
info: ObjectReference, // CoroutineInfo instance
|
|
||||||
threadRef: Field // reference to lastObservedThread
|
|
||||||
): ThreadReference? = info.getValue(threadRef) as? ThreadReference
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns list of stackTraceElements for the given CoroutineInfo's [ObjectReference]
|
|
||||||
*/
|
|
||||||
private fun getStackTrace(
|
|
||||||
info: ObjectReference
|
|
||||||
): List<StackTraceElement> {
|
|
||||||
val frameList = lastObservedStackTrace(info)
|
|
||||||
val tmpList = mutableListOf<StackTraceElement>()
|
|
||||||
for(it in 0 until sizeOf(frameList)) {
|
|
||||||
val frame = getElementFromList(frameList, it)
|
|
||||||
val ste = newStackTraceElement(frame)
|
|
||||||
tmpList.add(ste)
|
|
||||||
}
|
|
||||||
val mergedFrameList = enhanceStackTraceWithThreadDump(listOf(info, frameList))
|
|
||||||
val size = sizeOf(mergedFrameList)
|
|
||||||
|
|
||||||
val list = mutableListOf<StackTraceElement>()
|
|
||||||
|
|
||||||
for (it in 0 until size) {
|
|
||||||
val frame = getElementFromList(mergedFrameList, it)
|
|
||||||
val ste = newStackTraceElement(frame)
|
|
||||||
list.add(// 0, // add in the beginning // @TODO what's the point?
|
|
||||||
ste)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newStackTraceElement(frame: ObjectReference) =
|
|
||||||
StackTraceElement(
|
|
||||||
fetchClassName(frame),
|
|
||||||
fetchMethodName(frame),
|
|
||||||
fetchFileName(frame),
|
|
||||||
fetchLine(frame)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun fetchLine(instance: ObjectReference) =
|
|
||||||
(instance.getValue(refs.lineNumberFieldRef) as? IntegerValue)?.value() ?: -1
|
|
||||||
|
|
||||||
private fun fetchFileName(instance: ObjectReference) =
|
|
||||||
(instance.getValue(refs.fileNameFieldRef) as? StringReference)?.value() ?: ""
|
|
||||||
|
|
||||||
private fun fetchMethodName(instance: ObjectReference) =
|
|
||||||
(instance.getValue(refs.methodNameFieldRef) as? StringReference)?.value() ?: ""
|
|
||||||
|
|
||||||
private fun fetchClassName(instance: ObjectReference) =
|
|
||||||
(instance.getValue(refs.declaringClassFieldRef) as? StringReference)?.value() ?: ""
|
|
||||||
|
|
||||||
private fun lastObservedStackTrace(instance: ObjectReference) =
|
|
||||||
executionContext.invokeMethod(instance, refs.lastObservedStackTraceRef, emptyList()) as ObjectReference
|
|
||||||
|
|
||||||
private fun enhanceStackTraceWithThreadDump(args: List<ObjectReference>) =
|
|
||||||
executionContext.invokeMethod(
|
|
||||||
refs.debugProbesImplInstance,
|
|
||||||
refs.enhanceStackTraceWithThreadDumpRef, args) as ObjectReference
|
|
||||||
|
|
||||||
private fun sizeOf(args: ObjectReference): Int =
|
|
||||||
(executionContext.invokeMethod(args, refs.sizeRef, emptyList()) as IntegerValue).value()
|
|
||||||
|
|
||||||
private fun executionContext() : DefaultExecutionContext {
|
|
||||||
return DefaultExecutionContext(suspendContext as SuspendContextImpl, suspendContext.frameProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @TODO refactor later
|
|
||||||
* Holds ClassTypes, Methods, ObjectReferences and Fields for a particular jvm
|
|
||||||
*/
|
|
||||||
class ProcessReferences(executionContext: DefaultExecutionContext) {
|
|
||||||
// kotlinx.coroutines.debug.DebugProbes instance and methods
|
|
||||||
val debugProbesClsRef = executionContext.findClass("$DEBUG_PACKAGE.DebugProbes") as ClassType
|
|
||||||
val debugProbesImplClsRef = executionContext.findClass("$DEBUG_PACKAGE.internal.DebugProbesImpl") as ClassType
|
|
||||||
val coroutineNameClsRef = executionContext.findClass("kotlinx.coroutines.CoroutineName") as ClassType
|
|
||||||
val classClsRef = executionContext.findClass("java.lang.Object") as ClassType
|
|
||||||
val debugProbesImplInstance = with(debugProbesImplClsRef) { getValue(fieldByName("INSTANCE")) as ObjectReference }
|
|
||||||
val enhanceStackTraceWithThreadDumpRef: Method = debugProbesImplClsRef
|
|
||||||
.methodsByName("enhanceStackTraceWithThreadDump").single()
|
|
||||||
|
|
||||||
val dumpMethod: Method = debugProbesClsRef.concreteMethodByName("dumpCoroutinesInfo", "()Ljava/util/List;")
|
|
||||||
val instance = with(debugProbesClsRef) { getValue(fieldByName("INSTANCE")) as ObjectReference }
|
|
||||||
|
|
||||||
// CoroutineInfo
|
|
||||||
val coroutineInfoClsRef = executionContext.findClass("$DEBUG_PACKAGE.CoroutineInfo") as ClassType
|
|
||||||
val coroutineContextClsRef = executionContext.findClass("kotlin.coroutines.CoroutineContext") as InterfaceType
|
|
||||||
|
|
||||||
val getStateRef: Method = coroutineInfoClsRef.concreteMethodByName("getState", "()Lkotlinx/coroutines/debug/State;")
|
|
||||||
val getContextRef: Method = coroutineInfoClsRef.concreteMethodByName("getContext", "()Lkotlin/coroutines/CoroutineContext;")
|
|
||||||
val sequenceNumberFieldRef: Field = coroutineInfoClsRef.fieldByName("sequenceNumber")
|
|
||||||
val lastObservedStackTraceRef: Method = coroutineInfoClsRef.methodsByName("lastObservedStackTrace").single()
|
|
||||||
val getContextElement: Method = coroutineContextClsRef.methodsByName("get").single()
|
|
||||||
val getNameRef: Method = coroutineNameClsRef.methodsByName("getName").single()
|
|
||||||
val keyFieldRef = coroutineNameClsRef.fieldByName("Key")
|
|
||||||
val toString: Method = classClsRef.concreteMethodByName("toString", "()Ljava/lang/String;")
|
|
||||||
|
|
||||||
val lastObservedThreadFieldRef: Field = coroutineInfoClsRef.fieldByName("lastObservedThread")
|
|
||||||
val lastObservedFrameFieldRef: Field = coroutineInfoClsRef.fieldByName("lastObservedFrame") // continuation
|
|
||||||
|
|
||||||
// Methods for list
|
|
||||||
val listClsRef = executionContext.findClass("java.util.List") as InterfaceType
|
|
||||||
val sizeRef: Method = listClsRef.methodsByName("size").single()
|
|
||||||
val getRef: Method = listClsRef.methodsByName("get").single()
|
|
||||||
val stackTraceElementClsRef = executionContext.findClass("java.lang.StackTraceElement") as ClassType
|
|
||||||
|
|
||||||
// for StackTraceElement
|
|
||||||
val methodNameFieldRef: Field = stackTraceElementClsRef.fieldByName("methodName")
|
|
||||||
val declaringClassFieldRef: Field = stackTraceElementClsRef.fieldByName("declaringClass")
|
|
||||||
val fileNameFieldRef: Field = stackTraceElementClsRef.fieldByName("fileName")
|
|
||||||
val lineNumberFieldRef: Field = stackTraceElementClsRef.fieldByName("lineNumber")
|
|
||||||
|
|
||||||
// value
|
|
||||||
val keyFieldValueRef = coroutineNameClsRef.getValue(keyFieldRef) as ObjectReference
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineNameIdState
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.State
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.MirrorOfCoroutineContext
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
|
data class CoroutineHolder(
|
||||||
|
val value: ObjectReference?,
|
||||||
|
val info: CoroutineNameIdState,
|
||||||
|
val stackFrameItems: List<CoroutineStackFrameItem>
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun lookup(
|
||||||
|
value: ObjectReference?,
|
||||||
|
context: DefaultExecutionContext,
|
||||||
|
stackFrameItems: List<CoroutineStackFrameItem>
|
||||||
|
): CoroutineHolder? {
|
||||||
|
val state = state(value, context) ?: return null
|
||||||
|
val realState =
|
||||||
|
if (stackFrameItems.isEmpty()) state.copy(state = State.CREATED) else state.copy(state = State.SUSPENDED)
|
||||||
|
return CoroutineHolder(value, realState, stackFrameItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun state(value: ObjectReference?, context: DefaultExecutionContext): CoroutineNameIdState? {
|
||||||
|
value ?: return null
|
||||||
|
val reference = JavaLangMirror(context)
|
||||||
|
val standAloneCoroutineMirror = reference.standaloneCoroutine.mirror(value, context)
|
||||||
|
if (standAloneCoroutineMirror?.context is MirrorOfCoroutineContext) {
|
||||||
|
val id = standAloneCoroutineMirror.context.id
|
||||||
|
val toString = reference.string(value, context)
|
||||||
|
val r = """\w+\{(\w+)\}\@([\w\d]+)""".toRegex()
|
||||||
|
val matcher = r.toPattern().matcher(toString)
|
||||||
|
if (matcher.matches()) {
|
||||||
|
val state = stateOf(matcher.group(1))
|
||||||
|
val hexAddress = matcher.group(2)
|
||||||
|
return CoroutineNameIdState(standAloneCoroutineMirror.context.name, id?.toString() ?: hexAddress, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stateOf(state: String?): State =
|
||||||
|
when (state) {
|
||||||
|
"Active" -> State.RUNNING
|
||||||
|
"Cancelling" -> State.SUSPENDED_CANCELLING
|
||||||
|
"Completing" -> State.SUSPENDED_COMPLETING
|
||||||
|
"Cancelled" -> State.CANCELLED
|
||||||
|
"Completed" -> State.COMPLETED
|
||||||
|
"New" -> State.NEW
|
||||||
|
else -> State.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||||
|
|
||||||
|
interface CoroutineInfoProvider {
|
||||||
|
fun dumpCoroutinesInfo(): List<CoroutineInfoData>
|
||||||
|
}
|
||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcess
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
|
import com.intellij.debugger.jdi.ClassesByNameProvider
|
||||||
|
import com.intellij.debugger.jdi.GeneratedLocation
|
||||||
|
import com.intellij.util.containers.ContainerUtil
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineContext
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
|
class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, private val executionContext: DefaultExecutionContext) :
|
||||||
|
CoroutineInfoProvider {
|
||||||
|
private val coroutineContextReference: JavaLangMirror = JavaLangMirror(executionContext)
|
||||||
|
|
||||||
|
private val debugProbesImplClsRef = executionContext.findClass("$DEBUG_PACKAGE.internal.DebugProbesImpl") as ClassType
|
||||||
|
private val debugProbesImplInstance = with(debugProbesImplClsRef) { getValue(fieldByName("INSTANCE")) as ObjectReference }
|
||||||
|
private val enhanceStackTraceWithThreadDumpRef: Method = debugProbesImplClsRef
|
||||||
|
.methodsByName("enhanceStackTraceWithThreadDump").single()
|
||||||
|
|
||||||
|
private val dumpMethod: Method = debugProbesClsRef.concreteMethodByName("dumpCoroutinesInfo", "()Ljava/util/List;")
|
||||||
|
val instance = with(debugProbesClsRef) { getValue(fieldByName("INSTANCE")) as ObjectReference }
|
||||||
|
|
||||||
|
// CoroutineInfo
|
||||||
|
private val coroutineInfoClsRef = executionContext.findClass("$DEBUG_PACKAGE.CoroutineInfo") as ClassType
|
||||||
|
|
||||||
|
private val getStateRef: Method = coroutineInfoClsRef.concreteMethodByName("getState", "()Lkotlinx/coroutines/debug/State;")
|
||||||
|
private val getContextRef: Method = coroutineInfoClsRef.concreteMethodByName("getContext", "()Lkotlin/coroutines/CoroutineContext;")
|
||||||
|
private val lastObservedStackTraceRef: Method = coroutineInfoClsRef.methodsByName("lastObservedStackTrace").single()
|
||||||
|
|
||||||
|
private val sequenceNumberFieldRef: Field = coroutineInfoClsRef.fieldByName("sequenceNumber")
|
||||||
|
private val lastObservedThreadFieldRef: Field = coroutineInfoClsRef.fieldByName("lastObservedThread")
|
||||||
|
private val lastObservedFrameFieldRef: Field = coroutineInfoClsRef.fieldByName("lastObservedFrame") // continuation
|
||||||
|
|
||||||
|
// value
|
||||||
|
private val vm = executionContext.vm
|
||||||
|
private val classesByName = ClassesByNameProvider.createCache(vm.allClasses())
|
||||||
|
|
||||||
|
|
||||||
|
private val coroutineContext: CoroutineContext = CoroutineContext(executionContext)
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
@Suppress("unused")
|
||||||
|
fun install() =
|
||||||
|
executionContext.invokeMethodAsVoid(instance, "install")
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
@Suppress("unused")
|
||||||
|
fun uninstall() =
|
||||||
|
executionContext.invokeMethodAsVoid(instance, "uninstall")
|
||||||
|
|
||||||
|
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
||||||
|
val coroutinesInfo = executionContext.invokeMethodAsObject(instance, dumpMethod) ?: return emptyList()
|
||||||
|
executionContext.keepReference(coroutinesInfo)
|
||||||
|
val size = coroutineContextReference.sizeOf(coroutinesInfo, executionContext)
|
||||||
|
|
||||||
|
return MutableList(size) {
|
||||||
|
val elem = coroutineContextReference.elementFromList(coroutinesInfo, it, executionContext)
|
||||||
|
fetchCoroutineState(elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchCoroutineState(instance: ObjectReference): CoroutineInfoData {
|
||||||
|
val name = getName(instance)
|
||||||
|
val state = getState(instance)
|
||||||
|
val thread = getLastObservedThread(instance, lastObservedThreadFieldRef)
|
||||||
|
val lastObservedFrameFieldRef = instance.getValue(lastObservedFrameFieldRef) as? ObjectReference
|
||||||
|
val stackTrace = getStackTrace(instance)
|
||||||
|
val creationFrameSeparatorIndex = findCreationFrameIndex(stackTrace)
|
||||||
|
val coroutineStackTrace = stackTrace.take(creationFrameSeparatorIndex)
|
||||||
|
|
||||||
|
val coroutineStackTraceFrameItems = coroutineStackTrace.map {
|
||||||
|
SuspendCoroutineStackFrameItem(it, createLocation(it))
|
||||||
|
}
|
||||||
|
val creationStackTrace = stackTrace.subList(creationFrameSeparatorIndex + 1, stackTrace.size)
|
||||||
|
val creationStackTraceFrameItems = creationStackTrace.map {
|
||||||
|
CreationCoroutineStackFrameItem(it, createLocation(it))
|
||||||
|
}
|
||||||
|
val key = CoroutineNameIdState(name,"", State.valueOf(state))
|
||||||
|
|
||||||
|
return CoroutineInfoData(
|
||||||
|
key,
|
||||||
|
coroutineStackTraceFrameItems,
|
||||||
|
creationStackTraceFrameItems,
|
||||||
|
thread,
|
||||||
|
lastObservedFrameFieldRef
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createLocation(stackTraceElement: StackTraceElement): Location = findLocation(
|
||||||
|
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
|
||||||
|
stackTraceElement.methodName,
|
||||||
|
stackTraceElement.lineNumber
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun findLocation(
|
||||||
|
type: ReferenceType?,
|
||||||
|
methodName: String,
|
||||||
|
line: Int
|
||||||
|
): Location {
|
||||||
|
if (type != null && line >= 0) {
|
||||||
|
try {
|
||||||
|
val location = type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line).stream()
|
||||||
|
.filter { l: Location -> l.method().name() == methodName }
|
||||||
|
.findFirst().orElse(null)
|
||||||
|
if (location != null) {
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
} catch (ignored: AbsentInformationException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GeneratedLocation(executionContext.debugProcess, type, methodName, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find creation frame separator if any, returns last index if none found
|
||||||
|
*/
|
||||||
|
private fun findCreationFrameIndex(frames: List<StackTraceElement>): Int {
|
||||||
|
val index = frames.indexOfFirst { it.isCreationSeparatorFrame() }
|
||||||
|
return if (index < 0)
|
||||||
|
frames.lastIndex
|
||||||
|
else
|
||||||
|
index
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getName(
|
||||||
|
info: ObjectReference // CoroutineInfo instance
|
||||||
|
): String {
|
||||||
|
// equals to `coroutineInfo.context.get(CoroutineName).name`
|
||||||
|
val coroutineContextInst = executionContext.invokeMethod(
|
||||||
|
info,
|
||||||
|
getContextRef,
|
||||||
|
emptyList()
|
||||||
|
) as? ObjectReference ?: throw IllegalArgumentException("Coroutine context must not be null")
|
||||||
|
val context = coroutineContext.mirror(coroutineContextInst, executionContext)
|
||||||
|
val name = context?.name ?: "coroutine"
|
||||||
|
val id = (info.getValue(sequenceNumberFieldRef) as LongValue).value()
|
||||||
|
return "$name#$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getState(
|
||||||
|
info: ObjectReference // CoroutineInfo instance
|
||||||
|
): String {
|
||||||
|
// equals to `stringState = coroutineInfo.state.toString()`
|
||||||
|
val state = executionContext.invokeMethod(info, getStateRef, emptyList()) as ObjectReference
|
||||||
|
return coroutineContextReference.string(state, executionContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLastObservedThread(
|
||||||
|
info: ObjectReference, // CoroutineInfo instance
|
||||||
|
threadRef: Field // reference to lastObservedThread
|
||||||
|
): ThreadReference? = info.getValue(threadRef) as? ThreadReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of stackTraceElements for the given CoroutineInfo's [ObjectReference]
|
||||||
|
*/
|
||||||
|
private fun getStackTrace(
|
||||||
|
info: ObjectReference
|
||||||
|
): List<StackTraceElement> {
|
||||||
|
val frameList = lastObservedStackTrace(info)
|
||||||
|
// val tmpList = mutableListOf<StackTraceElement>()
|
||||||
|
// val sizeOfFrameList = coroutineContextReference.sizeOf(frameList, executionContext)
|
||||||
|
// for (it in 0 until sizeOfFrameList) {
|
||||||
|
// val frame = coroutineContextReference.elementFromList(frameList, it, executionContext)
|
||||||
|
// val ste = coroutineContextReference.stackTraceElement(frame)
|
||||||
|
// tmpList.add(ste)
|
||||||
|
// }
|
||||||
|
val mergedFrameList = enhanceStackTraceWithThreadDump(listOf(info, frameList))
|
||||||
|
val sizeOfMergedFrameList = coroutineContextReference.sizeOf(mergedFrameList, executionContext)
|
||||||
|
|
||||||
|
val list = mutableListOf<StackTraceElement>()
|
||||||
|
|
||||||
|
for (it in 0 until sizeOfMergedFrameList) {
|
||||||
|
val frame = coroutineContextReference.elementFromList(mergedFrameList, it, executionContext)
|
||||||
|
val ste = coroutineContextReference.stackTraceElement(frame)
|
||||||
|
list.add(// 0, // add in the beginning // @TODO what's the point?
|
||||||
|
ste
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lastObservedStackTrace(instance: ObjectReference) =
|
||||||
|
executionContext.invokeMethod(instance, lastObservedStackTraceRef, emptyList()) as ObjectReference
|
||||||
|
|
||||||
|
private fun enhanceStackTraceWithThreadDump(args: List<ObjectReference>) =
|
||||||
|
executionContext.invokeMethod(
|
||||||
|
debugProbesImplInstance,
|
||||||
|
enhanceStackTraceWithThreadDumpRef, args
|
||||||
|
) as ObjectReference
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DEBUG_PACKAGE = "kotlinx.coroutines.debug"
|
||||||
|
|
||||||
|
fun instance(executionContext: DefaultExecutionContext): CoroutineLibraryAgentProxy? {
|
||||||
|
try {
|
||||||
|
val debugProbesClsRef = executionContext.findClass("$DEBUG_PACKAGE.DebugProbes") ?: return null
|
||||||
|
if (debugProbesClsRef is ClassType) {
|
||||||
|
val instanceField = debugProbesClsRef.fieldByName("INSTANCE")
|
||||||
|
val debugProbes = debugProbesClsRef.getValue(instanceField) as ObjectReference
|
||||||
|
val f = debugProbes.referenceType().fieldByName("isInstalled")
|
||||||
|
val debugProbesActivated = if (f != null) (debugProbes.getValue(f) as BooleanValue).value() else true
|
||||||
|
if (debugProbesActivated)
|
||||||
|
return CoroutineLibraryAgentProxy(debugProbesClsRef, executionContext)
|
||||||
|
}
|
||||||
|
} catch (e: EvaluateException) {
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.registry.Registry
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CancellableContinuationImpl
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
|
class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : CoroutineInfoProvider {
|
||||||
|
val log by logger
|
||||||
|
val debugMetadataKtType = executionContext.findCoroutineMetadataType()
|
||||||
|
|
||||||
|
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
||||||
|
val vm = executionContext.vm
|
||||||
|
val resultList = mutableListOf<CoroutineInfoData>()
|
||||||
|
if (vm.virtualMachine.canGetInstanceInfo()) {
|
||||||
|
when (coroutineSwitch()) {
|
||||||
|
"DISPATCHED_CONTINUATION" -> dispatchedContinuation(resultList)
|
||||||
|
"CANCELLABLE_CONTINUATION" -> cancellableContinuation(resultList)
|
||||||
|
else -> dispatchedContinuation(resultList)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else
|
||||||
|
log.warn("Remote JVM doesn't support canGetInstanceInfo capability (perhaps JDK-8197943).")
|
||||||
|
return resultList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancellableContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
|
||||||
|
val dcClassTypeList = executionContext.findCancellableContinuationImplReferenceType()
|
||||||
|
if (dcClassTypeList?.size == 1) {
|
||||||
|
val dcClassType = dcClassTypeList.first()
|
||||||
|
val cci = CancellableContinuationImpl(executionContext)
|
||||||
|
|
||||||
|
val continuationList = dcClassType.instances(maxCoroutines())
|
||||||
|
for (cancellableContinuation in continuationList) {
|
||||||
|
val coroutineInfo = extractCancellableContinuation(cancellableContinuation, cci) ?: continue
|
||||||
|
resultList.add(coroutineInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extractCancellableContinuation(
|
||||||
|
dispatchedContinuation: ObjectReference,
|
||||||
|
ccMirrorProvider: CancellableContinuationImpl
|
||||||
|
): CoroutineInfoData? {
|
||||||
|
val mirror = ccMirrorProvider.mirror(dispatchedContinuation, executionContext) ?: return null
|
||||||
|
val continuation = mirror.delegate?.continuation ?: return null
|
||||||
|
val ch = ContinuationHolder(continuation, executionContext)
|
||||||
|
val coroutineWithRestoredStack = ch.getAsyncStackTraceIfAny() ?: return null
|
||||||
|
return CoroutineInfoData.suspendedCoroutineInfoData(coroutineWithRestoredStack, continuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchedContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
|
||||||
|
val dcClassTypeList = executionContext.findDispatchedContinuationReferenceType()
|
||||||
|
if (dcClassTypeList?.size == 1) {
|
||||||
|
val dcClassType = dcClassTypeList.first()
|
||||||
|
val continuationField = dcClassType.fieldByName("continuation") ?: return true
|
||||||
|
val continuationList = dcClassType.instances(maxCoroutines())
|
||||||
|
for (dispatchedContinuation in continuationList) {
|
||||||
|
val coroutineInfo = extractDispatchedContinuation(dispatchedContinuation, continuationField) ?: continue
|
||||||
|
resultList.add(coroutineInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? {
|
||||||
|
debugMetadataKtType ?: return null
|
||||||
|
val initialContinuation = dispatchedContinuation.getValue(continuation) as ObjectReference
|
||||||
|
val ch = ContinuationHolder(initialContinuation, executionContext)
|
||||||
|
val coroutineWithRestoredStack = ch.getAsyncStackTraceIfAny() ?: return null
|
||||||
|
return CoroutineInfoData.suspendedCoroutineInfoData(coroutineWithRestoredStack, initialContinuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun maxCoroutines() = Registry.intValue("kotlin.debugger.coroutines.max", 1000).toLong()
|
||||||
|
|
||||||
|
fun coroutineSwitch() = Registry.stringValue("kotlin.debugger.coroutines.switch")
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||||
|
import com.intellij.debugger.engine.JVMStackFrameInfoProvider
|
||||||
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
|
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||||
|
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||||
|
import com.intellij.xdebugger.frame.XCompositeNode
|
||||||
|
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.stackFrame.KotlinStackFrame
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coroutine exit frame represented by a stack frames
|
||||||
|
* invokeSuspend():-1
|
||||||
|
* resumeWith()
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CoroutinePreflightStackFrame(
|
||||||
|
val invokeSuspendFrame: StackFrameProxyImpl,
|
||||||
|
val resumeWithFrame: StackFrameProxyImpl,
|
||||||
|
val restoredStackFrame: List<CoroutineStackFrameItem>,
|
||||||
|
val stackFrameDescriptorImpl: StackFrameDescriptorImpl,
|
||||||
|
val threadPreCoroutineFrames: List<StackFrameProxyImpl>
|
||||||
|
) : KotlinStackFrame(stackFrameDescriptorImpl), JVMStackFrameInfoProvider {
|
||||||
|
|
||||||
|
override fun computeChildren(node: XCompositeNode) {
|
||||||
|
val childrenList = XValueChildrenList()
|
||||||
|
val firstRestoredCoroutineStackFrameItem = restoredStackFrame.firstOrNull() ?: return
|
||||||
|
firstRestoredCoroutineStackFrameItem.spilledVariables.forEach {
|
||||||
|
childrenList.add(it)
|
||||||
|
}
|
||||||
|
node.addChildren(childrenList, false)
|
||||||
|
super.computeChildren(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isInLibraryContent() =
|
||||||
|
false
|
||||||
|
|
||||||
|
override fun isSynthetic() =
|
||||||
|
false
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun preflight(
|
||||||
|
invokeSuspendFrame: StackFrameProxyImpl,
|
||||||
|
resumeWithFrame: StackFrameProxyImpl,
|
||||||
|
restoredStackFrame: List<CoroutineStackFrameItem>,
|
||||||
|
originalFrames: List<StackFrameProxyImpl>
|
||||||
|
): CoroutinePreflightStackFrame? {
|
||||||
|
val topRestoredFrame = restoredStackFrame.firstOrNull() ?: return null
|
||||||
|
val descriptor = StackFrameDescriptorImpl(
|
||||||
|
LocationStackFrameProxyImpl(topRestoredFrame.location, invokeSuspendFrame), MethodsTracker()
|
||||||
|
)
|
||||||
|
return CoroutinePreflightStackFrame(invokeSuspendFrame, resumeWithFrame, restoredStackFrame, descriptor, originalFrames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
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.openapi.project.Project
|
||||||
|
import com.intellij.psi.JavaPsiFacade
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.xdebugger.XDebugSession
|
||||||
|
import com.intellij.xdebugger.XDebuggerUtil
|
||||||
|
import com.intellij.xdebugger.XSourcePosition
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineBuilder
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
|
||||||
|
|
||||||
|
fun Method.isInvokeSuspend(): Boolean =
|
||||||
|
name() == "invokeSuspend" && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;"
|
||||||
|
|
||||||
|
fun Method.isContinuation() =
|
||||||
|
isInvokeSuspend() && declaringType().isContinuation() /* Perhaps need to check for "Lkotlin/coroutines/Continuation;)" in signature() ? */
|
||||||
|
|
||||||
|
fun Method.isSuspendLambda() =
|
||||||
|
isInvokeSuspend() && declaringType().isSuspendLambda()
|
||||||
|
|
||||||
|
fun Method.isResumeWith() =
|
||||||
|
name() == "resumeWith" && signature() == "(Ljava/lang/Object;)V" && (declaringType().isSuspendLambda() || declaringType().isContinuation())
|
||||||
|
|
||||||
|
fun Location.isPreFlight(): Boolean {
|
||||||
|
val method = safeMethod() ?: return false
|
||||||
|
return method.isSuspendLambda() || method.isContinuation()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ReferenceType.isContinuation() =
|
||||||
|
isBaseContinuationImpl() || isSubtype("kotlin.coroutines.Continuation")
|
||||||
|
|
||||||
|
fun Type.isBaseContinuationImpl() =
|
||||||
|
isSubtype("kotlin.coroutines.jvm.internal.BaseContinuationImpl")
|
||||||
|
|
||||||
|
fun Type.isAbstractCoroutine() =
|
||||||
|
isSubtype("kotlinx.coroutines.AbstractCoroutine")
|
||||||
|
|
||||||
|
fun Type.isSubTypeOrSame(className: String) =
|
||||||
|
name() == className || isSubtype(className)
|
||||||
|
|
||||||
|
fun ReferenceType.isSuspendLambda() =
|
||||||
|
SUSPEND_LAMBDA_CLASSES.any { isSubtype(it) }
|
||||||
|
|
||||||
|
fun Location.isPreExitFrame() =
|
||||||
|
safeMethod()?.isResumeWith() ?: false
|
||||||
|
|
||||||
|
fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
|
||||||
|
val continuationVariable = safeVisibleVariableByName(variableName) ?: return null
|
||||||
|
return getValue(continuationVariable) as? ObjectReference ?: return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun StackFrameProxyImpl.completionVariableValue(): ObjectReference? =
|
||||||
|
variableValue("completion")
|
||||||
|
|
||||||
|
private fun Method.isGetCOROUTINE_SUSPENDED() =
|
||||||
|
signature() == "()Ljava/lang/Object;" && name() == "getCOROUTINE_SUSPENDED" && declaringType().name() == "kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsKt"
|
||||||
|
|
||||||
|
fun DefaultExecutionContext.findCoroutineMetadataType() =
|
||||||
|
debugProcess.invokeInManagerThread { findClassSafe("kotlin.coroutines.jvm.internal.DebugMetadataKt") }
|
||||||
|
|
||||||
|
fun DefaultExecutionContext.findCoroutineAnnotationMetadataType() =
|
||||||
|
debugProcess.invokeInManagerThread { findClassSafe("kotlin.coroutines.jvm.internal.DebugMetadata") as InterfaceType? }
|
||||||
|
|
||||||
|
fun DefaultExecutionContext.findDispatchedContinuationReferenceType(): List<ReferenceType>? =
|
||||||
|
vm.classesByName("kotlinx.coroutines.DispatchedContinuation")
|
||||||
|
|
||||||
|
fun DefaultExecutionContext.findCancellableContinuationImplReferenceType(): List<ReferenceType>? =
|
||||||
|
vm.classesByName("kotlinx.coroutines.CancellableContinuationImpl")
|
||||||
|
|
||||||
|
fun findGetCoroutineSuspended(frames: List<StackFrameProxyImpl>) =
|
||||||
|
frames.indexOfFirst { it.safeLocation()?.safeMethod()?.isGetCOROUTINE_SUSPENDED() == true }
|
||||||
|
|
||||||
|
fun StackTraceElement.isCreationSeparatorFrame() =
|
||||||
|
className.startsWith(CoroutineBuilder.CREATION_STACK_TRACE_SEPARATOR)
|
||||||
|
|
||||||
|
fun StackTraceElement.findPosition(project: Project): XSourcePosition? =
|
||||||
|
getPosition(project, className, lineNumber)
|
||||||
|
|
||||||
|
fun Location.findPosition(project: Project) =
|
||||||
|
getPosition(project, declaringType().name(), lineNumber())
|
||||||
|
|
||||||
|
fun ClassType.completionField() =
|
||||||
|
fieldByName("completion") as Field?
|
||||||
|
|
||||||
|
private fun getPosition(project: Project, className: String, lineNumber: Int): XSourcePosition? {
|
||||||
|
val psiFacade = JavaPsiFacade.getInstance(project)
|
||||||
|
val psiClass = psiFacade.findClass(
|
||||||
|
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 localLineNumber = if (lineNumber > 0) lineNumber - 1 else return null
|
||||||
|
return XDebuggerUtil.getInstance().createPosition(classFile, localLineNumber)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Finds previous Continuation for this Continuation (completion field in BaseContinuationImpl)
|
||||||
|
* @return null if given ObjectReference is not a BaseContinuationImpl instance or completion is null
|
||||||
|
*/
|
||||||
|
fun getNextFrame(context: DefaultExecutionContext, continuation: ObjectReference): ObjectReference? {
|
||||||
|
if (!continuation.type().isBaseContinuationImpl())
|
||||||
|
return null
|
||||||
|
val type = continuation.type() as ClassType
|
||||||
|
val next = type.concreteMethodByName("getCompletion", "()Lkotlin/coroutines/Continuation;")
|
||||||
|
return context.invokeMethod(continuation, next, emptyList()) as? ObjectReference
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SuspendContextImpl.executionContext() =
|
||||||
|
invokeInManagerThread { DefaultExecutionContext(EvaluationContextImpl(this, this.frameProxy)) }
|
||||||
|
|
||||||
|
fun <T : Any> SuspendContextImpl.invokeInManagerThread(f: () -> T?) : T? =
|
||||||
|
debugProcess.invokeInManagerThread { f() }
|
||||||
|
|
||||||
|
fun ThreadReferenceProxyImpl.supportsEvaluation(): Boolean =
|
||||||
|
threadReference?.isSuspended ?: false
|
||||||
|
|
||||||
|
fun SuspendContextImpl.supportsEvaluation() =
|
||||||
|
this.debugProcess.canRunEvaluation || isUnitTestMode()
|
||||||
|
|
||||||
|
fun XDebugSession.suspendContextImpl() =
|
||||||
|
suspendContext as SuspendContextImpl
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import com.sun.jdi.ArrayReference
|
||||||
|
import com.sun.jdi.ClassType
|
||||||
|
import com.sun.jdi.ObjectReference
|
||||||
|
import com.sun.jdi.StringReference
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
|
data class FieldVariable(val fieldName: String, val variableName: String) {
|
||||||
|
companion object {
|
||||||
|
fun extractFromContinuation(context: DefaultExecutionContext, continuation: ObjectReference, debugMetadataKtType: ClassType?) : List<FieldVariable> {
|
||||||
|
val metadataType = debugMetadataKtType ?: context.findCoroutineMetadataType() ?: return emptyList()
|
||||||
|
val rawSpilledVariables =
|
||||||
|
context.invokeMethodAsArray(
|
||||||
|
metadataType,
|
||||||
|
"getSpilledVariableFieldMapping",
|
||||||
|
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;",
|
||||||
|
continuation
|
||||||
|
) ?: return emptyList()
|
||||||
|
|
||||||
|
context.keepReference(rawSpilledVariables)
|
||||||
|
|
||||||
|
val length = rawSpilledVariables.length() / 2
|
||||||
|
val fieldVariables = ArrayList<FieldVariable>()
|
||||||
|
for (index in 0 until length) {
|
||||||
|
fieldVariables.add(getFieldVariableName(rawSpilledVariables, index) ?: continue)
|
||||||
|
}
|
||||||
|
return fieldVariables
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getFieldVariableName(rawSpilledVariables: ArrayReference, index: Int): FieldVariable? {
|
||||||
|
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: return null
|
||||||
|
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: return null
|
||||||
|
return FieldVariable(fieldName, variableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy
|
||||||
|
|
||||||
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
|
import com.sun.jdi.Location
|
||||||
|
|
||||||
|
class LocationStackFrameProxyImpl(val location: Location, frame: StackFrameProxyImpl) :
|
||||||
|
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom) {
|
||||||
|
override fun location(): Location {
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
}
|
||||||
-94
@@ -1,94 +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.proxy
|
|
||||||
|
|
||||||
import com.sun.jdi.*
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.isSubtype
|
|
||||||
|
|
||||||
class LookupContinuation(val context: DefaultExecutionContext, val frame: StackTraceElement) {
|
|
||||||
|
|
||||||
private fun suspendOrInvokeSuspend(method: Method): Boolean =
|
|
||||||
"Lkotlin/coroutines/Continuation;)" in method.signature() ||
|
|
||||||
(method.name() == "invokeSuspend" && method.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;") // suspend fun or invokeSuspend
|
|
||||||
|
|
||||||
private fun findMethod(): Method {
|
|
||||||
val clazz = context.findClass(frame.className) as ClassType
|
|
||||||
val method = clazz.methodsByName(frame.methodName).last {
|
|
||||||
val loc = it.location().lineNumber()
|
|
||||||
loc < 0 && frame.lineNumber < 0 || loc > 0 && loc <= frame.lineNumber
|
|
||||||
} // pick correct method if an overloaded one is given
|
|
||||||
return method
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isApplicable(): Boolean {
|
|
||||||
val method = findMethod()
|
|
||||||
return suspendOrInvokeSuspend(method)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find continuation for the [frame]
|
|
||||||
* Gets current CoroutineInfo.lastObservedFrame and finds next frames in it until null or needed stackTraceElement is found
|
|
||||||
* @return null if matching continuation is not found or is not BaseContinuationImpl
|
|
||||||
*/
|
|
||||||
fun findContinuation(initialContinuation: ObjectReference?): ObjectReference? {
|
|
||||||
if (!isApplicable())
|
|
||||||
return null
|
|
||||||
|
|
||||||
var continuation = initialContinuation ?: return null
|
|
||||||
val baseType = "kotlin.coroutines.jvm.internal.BaseContinuationImpl"
|
|
||||||
val getTrace = (continuation.type() as ClassType).concreteMethodByName(
|
|
||||||
"getStackTraceElement",
|
|
||||||
"()Ljava/lang/StackTraceElement;"
|
|
||||||
)
|
|
||||||
val stackTraceType = context.findClass("java.lang.StackTraceElement") as ClassType
|
|
||||||
val getClassName = stackTraceType.concreteMethodByName("getClassName", "()Ljava/lang/String;")
|
|
||||||
val getLineNumber = stackTraceType.concreteMethodByName("getLineNumber", "()I")
|
|
||||||
val className = {
|
|
||||||
val trace = context.invokeMethod(continuation, getTrace, emptyList()) as? ObjectReference
|
|
||||||
if (trace != null)
|
|
||||||
(context.invokeMethod(trace, getClassName, emptyList()) as StringReference).value()
|
|
||||||
else null
|
|
||||||
}
|
|
||||||
val lineNumber = {
|
|
||||||
val trace = context.invokeMethod(continuation, getTrace, emptyList()) as? ObjectReference
|
|
||||||
if (trace != null)
|
|
||||||
(context.invokeMethod(trace, getLineNumber, emptyList()) as IntegerValue).value()
|
|
||||||
else null
|
|
||||||
}
|
|
||||||
|
|
||||||
while (continuation.type().isSubtype(baseType)
|
|
||||||
&& (frame.className != className() || frame.lineNumber != lineNumber())
|
|
||||||
) {
|
|
||||||
// while continuation is BaseContinuationImpl and it's frame equals to the current
|
|
||||||
continuation = getNextFrame(continuation, context) ?: return null
|
|
||||||
}
|
|
||||||
return if (continuation.type().isSubtype(baseType)) continuation else null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds previous Continuation for this Continuation (completion field in BaseContinuationImpl)
|
|
||||||
* @return null if given ObjectReference is not a BaseContinuationImpl instance or completion is null
|
|
||||||
*/
|
|
||||||
private fun getNextFrame(continuation: ObjectReference, context: DefaultExecutionContext): ObjectReference? {
|
|
||||||
val type = continuation.type() as ClassType
|
|
||||||
if (!type.isSubtype("kotlin.coroutines.jvm.internal.BaseContinuationImpl")) return null
|
|
||||||
val next = type.concreteMethodByName("getCompletion", "()Lkotlin/coroutines/Continuation;")
|
|
||||||
return context.invokeMethod(continuation, next, emptyList()) as? ObjectReference
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findGetStackTraceElementMethodRef(continuation: ObjectReference): Method =
|
|
||||||
(continuation.type() as ClassType).concreteMethodByName("getStackTraceElement", "()Ljava/lang/StackTraceElement;")
|
|
||||||
|
|
||||||
fun createAsyncStackTraceContext(stackTraceElementMethodRef: Method) =
|
|
||||||
AsyncStackTraceContext(context, stackTraceElementMethodRef)
|
|
||||||
|
|
||||||
fun createAsyncStackTraceContext(continuation: ObjectReference): AsyncStackTraceContext? {
|
|
||||||
val getStackTraceElementMethodRef = findGetStackTraceElementMethodRef(continuation)
|
|
||||||
return createAsyncStackTraceContext(getStackTraceElementMethodRef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+10
-4
@@ -6,28 +6,34 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||||
|
|
||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.engine.JavaDebugProcess
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
|
||||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||||
import com.intellij.debugger.impl.PrioritizedTask
|
import com.intellij.debugger.impl.PrioritizedTask
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.application.ModalityState
|
import com.intellij.openapi.application.ModalityState
|
||||||
import com.intellij.openapi.util.Computable
|
import com.intellij.openapi.util.Computable
|
||||||
|
import com.intellij.xdebugger.XDebugProcess
|
||||||
|
import com.intellij.xdebugger.XDebugSession
|
||||||
import com.intellij.xdebugger.frame.XSuspendContext
|
import com.intellij.xdebugger.frame.XSuspendContext
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
|
|
||||||
class ManagerThreadExecutor(val debugProcess: DebugProcessImpl) {
|
class ManagerThreadExecutor(val debugProcess: DebugProcessImpl) {
|
||||||
fun on(suspendContext: SuspendContextImpl, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
|
|
||||||
ManagerThreadExecutorInstance(suspendContext, priority)
|
constructor(session: XDebugSession) : this(session.debugProcess)
|
||||||
|
|
||||||
|
constructor(debugProcess: XDebugProcess) : this((debugProcess as JavaDebugProcess).debuggerSession.process)
|
||||||
|
|
||||||
fun on(suspendContext: XSuspendContext, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
|
fun on(suspendContext: XSuspendContext, priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL) =
|
||||||
ManagerThreadExecutorInstance(suspendContext as SuspendContextImpl, priority)
|
ManagerThreadExecutorInstance(suspendContext, priority)
|
||||||
|
|
||||||
inner class ManagerThreadExecutorInstance(
|
inner class ManagerThreadExecutorInstance(
|
||||||
val suspendContext: SuspendContextImpl,
|
val suspendContext: SuspendContextImpl,
|
||||||
val priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL
|
val priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
constructor(sc: XSuspendContext, priority: PrioritizedTask.Priority) : this(sc as SuspendContextImpl, priority)
|
||||||
|
|
||||||
fun schedule(f: (SuspendContextImpl) -> Unit) {
|
fun schedule(f: (SuspendContextImpl) -> Unit) {
|
||||||
val suspendContextCommand = object : SuspendContextCommandImpl(suspendContext) {
|
val suspendContextCommand = object : SuspendContextCommandImpl(suspendContext) {
|
||||||
override fun getPriority() = this@ManagerThreadExecutorInstance.priority
|
override fun getPriority() = this@ManagerThreadExecutorInstance.priority
|
||||||
|
|||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy.mirror
|
||||||
|
|
||||||
|
import com.sun.jdi.Method
|
||||||
|
import com.sun.jdi.ObjectReference
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
|
class CoroutineContext(context: DefaultExecutionContext) :
|
||||||
|
BaseMirror<MirrorOfCoroutineContext>("kotlin.coroutines.CoroutineContext", context) {
|
||||||
|
val coroutineNameRef = CoroutineName(context)
|
||||||
|
val coroutineIdRef = CoroutineId(context)
|
||||||
|
val jobRef = Job(context)
|
||||||
|
val getContextElement: Method = makeMethod("get")
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineContext? {
|
||||||
|
val coroutineName = getElementValue(value, context, coroutineNameRef) ?: "coroutine"
|
||||||
|
val coroutineId = getElementValue(value, context, coroutineIdRef)
|
||||||
|
val job = getElementValue(value, context, jobRef)
|
||||||
|
return MirrorOfCoroutineContext(value, coroutineName, coroutineId, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> getElementValue(value: ObjectReference, context: DefaultExecutionContext, keyProvider: ContextKey<T>): T? {
|
||||||
|
val elementValue = objectValue(value, getContextElement, context, keyProvider.key()) ?: return null
|
||||||
|
return keyProvider.mirror(elementValue, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MirrorOfCoroutineContext(
|
||||||
|
val that: ObjectReference,
|
||||||
|
val name: String,
|
||||||
|
val id: Long?,
|
||||||
|
val job: ObjectReference?
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract class ContextKey<T>(name: String, context: DefaultExecutionContext) : BaseMirror<T>(name, context) {
|
||||||
|
abstract fun key() : ObjectReference
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoroutineName(context: DefaultExecutionContext) : ContextKey<String>("kotlinx.coroutines.CoroutineName", context) {
|
||||||
|
val key = staticObjectValue("Key")
|
||||||
|
val getNameRef: Method = makeMethod("getName")
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): String? {
|
||||||
|
return stringValue(value, getNameRef, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun key() = key
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoroutineId(context: DefaultExecutionContext) : ContextKey<Long>("kotlinx.coroutines.CoroutineId", context) {
|
||||||
|
val key = staticObjectValue("Key")
|
||||||
|
val getIdRef: Method = makeMethod("getId")
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): Long? {
|
||||||
|
return longValue(value, getIdRef, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun key() = key
|
||||||
|
}
|
||||||
|
|
||||||
|
class Job(context: DefaultExecutionContext) : ContextKey<ObjectReference>("kotlinx.coroutines.Job", context) {
|
||||||
|
val key = staticObjectValue("Key")
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): ObjectReference? {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun key() = key
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy.mirror
|
||||||
|
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.isSubTypeOrSame
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
|
|
||||||
|
abstract class BaseMirror<T>(val name: String, context: DefaultExecutionContext) {
|
||||||
|
val log by logger
|
||||||
|
protected val cls = context.findClass(name) ?: throw IllegalStateException("Can't find class ${name} in remote jvm.")
|
||||||
|
|
||||||
|
fun makeField(fieldName: String): Field =
|
||||||
|
cls.fieldByName(fieldName) // childContinuation
|
||||||
|
|
||||||
|
fun makeMethod(methodName: String): Method =
|
||||||
|
cls.methodsByName(methodName).single()
|
||||||
|
|
||||||
|
fun isCompatible(value: ObjectReference) =
|
||||||
|
value.referenceType().isSubTypeOrSame(name)
|
||||||
|
|
||||||
|
fun mirror(value: ObjectReference, context: DefaultExecutionContext): T? {
|
||||||
|
if (!isCompatible(value)) {
|
||||||
|
log.warn("Value ${value.referenceType()} is not compatible with $name.")
|
||||||
|
return null
|
||||||
|
} else
|
||||||
|
return fetchMirror(value, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun staticObjectValue(fieldName: String): ObjectReference {
|
||||||
|
val keyFieldRef = makeField(fieldName)
|
||||||
|
return cls.getValue(keyFieldRef) as ObjectReference
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stringValue(value: ObjectReference, field: Field) =
|
||||||
|
(value.getValue(field) as StringReference).value()
|
||||||
|
|
||||||
|
fun stringValue(value: ObjectReference, method: Method, context: DefaultExecutionContext) =
|
||||||
|
(context.invokeMethod(value, method, emptyList()) as StringReference).value()
|
||||||
|
|
||||||
|
fun objectValue(value: ObjectReference, method: Method, context: DefaultExecutionContext, vararg values: Value) =
|
||||||
|
context.invokeMethodAsObject(value, method, *values)
|
||||||
|
|
||||||
|
fun longValue(value: ObjectReference, method: Method, context: DefaultExecutionContext, vararg values: Value) =
|
||||||
|
(context.invokeMethodAsObject(value, method, *values) as LongValue).longValue()
|
||||||
|
|
||||||
|
fun objectValue(value: ObjectReference, field: Field) =
|
||||||
|
value.getValue(field) as ObjectReference
|
||||||
|
|
||||||
|
fun intValue(value: ObjectReference, field: Field) =
|
||||||
|
(value.getValue(field) as IntegerValue).intValue()
|
||||||
|
|
||||||
|
fun longValue(value: ObjectReference, field: Field) =
|
||||||
|
(value.getValue(field) as LongValue).longValue()
|
||||||
|
|
||||||
|
protected abstract fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): T?
|
||||||
|
}
|
||||||
|
|
||||||
|
class StandaloneCoroutine(context: DefaultExecutionContext) :
|
||||||
|
BaseMirror<MirrorOfStandaloneCoroutine>("kotlinx.coroutines.StandaloneCoroutine", context) {
|
||||||
|
private val coroutineContextMirror = CoroutineContext(context)
|
||||||
|
private val childContinuationMirror = ChildContinuation(context)
|
||||||
|
private val stateFieldRef: Field = makeField("_state") // childContinuation
|
||||||
|
private val contextFieldRef: Field = makeField("context")
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStandaloneCoroutine {
|
||||||
|
val state = objectValue(value, stateFieldRef)
|
||||||
|
val childcontinuation = childContinuationMirror.mirror(state, context)
|
||||||
|
val cc = objectValue(value, contextFieldRef)
|
||||||
|
val coroutineContext = coroutineContextMirror.mirror(cc, context)
|
||||||
|
return MirrorOfStandaloneCoroutine(value, childcontinuation, coroutineContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MirrorOfStandaloneCoroutine(
|
||||||
|
val that: ObjectReference,
|
||||||
|
val state: MirrorOfChildContinuation?,
|
||||||
|
val context: MirrorOfCoroutineContext?
|
||||||
|
)
|
||||||
|
|
||||||
|
class ChildContinuation(context: DefaultExecutionContext) :
|
||||||
|
BaseMirror<MirrorOfChildContinuation>("kotlinx.coroutines.ChildContinuation", context) {
|
||||||
|
private val childContinuationMirror = CancellableContinuationImpl(context)
|
||||||
|
private val childFieldRef: Field = makeField("child") // cancellableContinuationImpl
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfChildContinuation? {
|
||||||
|
val child = objectValue(value, childFieldRef)
|
||||||
|
return MirrorOfChildContinuation(value, childContinuationMirror.mirror(child, context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MirrorOfChildContinuation(
|
||||||
|
val that: ObjectReference,
|
||||||
|
val child: MirrorOfCancellableContinuationImpl?
|
||||||
|
)
|
||||||
|
|
||||||
|
class CancellableContinuationImpl(context: DefaultExecutionContext) :
|
||||||
|
BaseMirror<MirrorOfCancellableContinuationImpl>("kotlinx.coroutines.CancellableContinuationImpl", context) {
|
||||||
|
private val coroutineContextMirror = CoroutineContext(context)
|
||||||
|
private val dispatchedContinuationtMirror = DispatchedContinuation(context)
|
||||||
|
private val decisionFieldRef: Field = makeField("_decision")
|
||||||
|
private val delegateFieldRef: Field = makeField("delegate") // DispatchedContinuation
|
||||||
|
private val resumeModeFieldRef: Field = makeField("resumeMode")
|
||||||
|
private val submissionTimeFieldRef: Field = makeField("submissionTime")
|
||||||
|
private val contextFieldRef: Field = makeField("context")
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCancellableContinuationImpl? {
|
||||||
|
val decision = intValue(value, decisionFieldRef)
|
||||||
|
val dispatchedContinuation = dispatchedContinuationtMirror.mirror(objectValue(value, delegateFieldRef), context)
|
||||||
|
val submissionTime = longValue(value, submissionTimeFieldRef)
|
||||||
|
val resumeMode = intValue(value, resumeModeFieldRef)
|
||||||
|
val coroutineContext = objectValue(value, contextFieldRef)
|
||||||
|
val contextMirror = coroutineContextMirror.mirror(coroutineContext, context)
|
||||||
|
return MirrorOfCancellableContinuationImpl(value, decision, dispatchedContinuation, resumeMode, submissionTime, contextMirror)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MirrorOfCancellableContinuationImpl(
|
||||||
|
val that: ObjectReference,
|
||||||
|
val decision: Int,
|
||||||
|
val delegate: MirrorOfDispatchedContinuation?,
|
||||||
|
val resumeMode: Int,
|
||||||
|
val submissionTyme: Long,
|
||||||
|
val jobContext: MirrorOfCoroutineContext?
|
||||||
|
)
|
||||||
|
|
||||||
|
class DispatchedContinuation(context: DefaultExecutionContext) :
|
||||||
|
BaseMirror<MirrorOfDispatchedContinuation>("kotlinx.coroutines.DispatchedContinuation", context) {
|
||||||
|
private val decisionFieldRef: Field = makeField("continuation")
|
||||||
|
|
||||||
|
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDispatchedContinuation? {
|
||||||
|
val continuation = objectValue(value, decisionFieldRef)
|
||||||
|
return MirrorOfDispatchedContinuation(value, continuation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MirrorOfDispatchedContinuation(
|
||||||
|
val that: ObjectReference,
|
||||||
|
val continuation: ObjectReference?,
|
||||||
|
)
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.proxy.mirror
|
||||||
|
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||||
|
|
||||||
|
class JavaLangMirror(context: DefaultExecutionContext) {
|
||||||
|
// java.lang.Object
|
||||||
|
val classClsRef = context.findClass("java.lang.Object") as ClassType
|
||||||
|
val toString: Method = classClsRef.concreteMethodByName("toString", "()Ljava/lang/String;")
|
||||||
|
|
||||||
|
// java.util.List
|
||||||
|
private val listClsRef = context.findClass("java.util.List") as InterfaceType
|
||||||
|
private val sizeRef: Method = listClsRef.methodsByName("size").single()
|
||||||
|
private val getRef: Method = listClsRef.methodsByName("get").single()
|
||||||
|
|
||||||
|
// java.lang.StackTraceElement
|
||||||
|
private val stackTraceElementClsRef = context.findClass("java.lang.StackTraceElement") as ClassType
|
||||||
|
private val methodNameFieldRef: Field = stackTraceElementClsRef.fieldByName("methodName")
|
||||||
|
private val declaringClassFieldRef: Field = stackTraceElementClsRef.fieldByName("declaringClass")
|
||||||
|
private val fileNameFieldRef: Field = stackTraceElementClsRef.fieldByName("fileName")
|
||||||
|
private val lineNumberFieldRef: Field = stackTraceElementClsRef.fieldByName("lineNumber")
|
||||||
|
|
||||||
|
// java.lang.Class
|
||||||
|
val classType = context.findClass("java.lang.Class") as ClassType
|
||||||
|
|
||||||
|
val standaloneCoroutine = StandaloneCoroutine(context)
|
||||||
|
|
||||||
|
|
||||||
|
fun string(state: ObjectReference, context: DefaultExecutionContext): String =
|
||||||
|
(context.invokeMethod(state, toString, emptyList()) as StringReference).value()
|
||||||
|
|
||||||
|
fun elementFromList(instance: ObjectReference, num: Int, context: DefaultExecutionContext) =
|
||||||
|
context.invokeMethod(
|
||||||
|
instance, getRef,
|
||||||
|
listOf(context.vm.virtualMachine.mirrorOf(num))
|
||||||
|
) as ObjectReference
|
||||||
|
|
||||||
|
fun sizeOf(args: ObjectReference, context: DefaultExecutionContext): Int =
|
||||||
|
(context.invokeMethod(args, sizeRef, emptyList()) as IntegerValue).value()
|
||||||
|
|
||||||
|
fun stackTraceElement(frame: ObjectReference) =
|
||||||
|
StackTraceElement(
|
||||||
|
fetchClassName(frame),
|
||||||
|
fetchMethodName(frame),
|
||||||
|
fetchFileName(frame),
|
||||||
|
fetchLine(frame)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun fetchLine(instance: ObjectReference) =
|
||||||
|
(instance.getValue(lineNumberFieldRef) as? IntegerValue)?.value() ?: -1
|
||||||
|
|
||||||
|
private fun fetchFileName(instance: ObjectReference) =
|
||||||
|
(instance.getValue(fileNameFieldRef) as? StringReference)?.value() ?: ""
|
||||||
|
|
||||||
|
private fun fetchMethodName(instance: ObjectReference) =
|
||||||
|
(instance.getValue(methodNameFieldRef) as? StringReference)?.value() ?: ""
|
||||||
|
|
||||||
|
private fun fetchClassName(instance: ObjectReference) =
|
||||||
|
(instance.getValue(declaringClassFieldRef) as? StringReference)?.value() ?: ""
|
||||||
|
}
|
||||||
-6
@@ -5,9 +5,6 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.util
|
package org.jetbrains.kotlin.idea.debugger.coroutine.util
|
||||||
|
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
|
||||||
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.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.MessageType
|
import com.intellij.openapi.ui.MessageType
|
||||||
import com.intellij.psi.JavaPsiFacade
|
import com.intellij.psi.JavaPsiFacade
|
||||||
@@ -34,9 +31,6 @@ fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSource
|
|||||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
|
||||||
StackFrameDescriptorImpl(proxy, MethodsTracker())
|
|
||||||
|
|
||||||
class ProjectNotification(val project: Project) {
|
class ProjectNotification(val project: Project) {
|
||||||
fun error(message: String) =
|
fun error(message: String) =
|
||||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project)
|
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project)
|
||||||
|
|||||||
+10
-9
@@ -34,6 +34,7 @@ import com.intellij.util.PlatformIcons
|
|||||||
import com.intellij.util.ui.EmptyIcon
|
import com.intellij.util.ui.EmptyIcon
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.State
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import java.awt.datatransfer.StringSelection
|
import java.awt.datatransfer.StringSelection
|
||||||
@@ -143,7 +144,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
|||||||
var index = 0
|
var index = 0
|
||||||
val states = if (UISettings.instance.state.mergeEqualStackTraces) mergedDump else dump
|
val states = if (UISettings.instance.state.mergeEqualStackTraces) mergedDump else dump
|
||||||
for (state in states) {
|
for (state in states) {
|
||||||
if (StringUtil.containsIgnoreCase(state.stringStackTrace, text) || StringUtil.containsIgnoreCase(state.name, text)) {
|
if (StringUtil.containsIgnoreCase(state.stringStackTrace, text) || StringUtil.containsIgnoreCase(state.key.name, text)) {
|
||||||
model.addElement(state)
|
model.addElement(state)
|
||||||
if (selection === state) {
|
if (selection === state) {
|
||||||
selectedIndex = index
|
selectedIndex = index
|
||||||
@@ -180,9 +181,9 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
|||||||
override fun getData(dataId: String): Any? = if (PlatformDataKeys.EXPORTER_TO_TEXT_FILE.`is`(dataId)) exporterToTextFile else null
|
override fun getData(dataId: String): Any? = if (PlatformDataKeys.EXPORTER_TO_TEXT_FILE.`is`(dataId)) exporterToTextFile else null
|
||||||
|
|
||||||
private fun getCoroutineStateIcon(infoData: CoroutineInfoData): Icon {
|
private fun getCoroutineStateIcon(infoData: CoroutineInfoData): Icon {
|
||||||
return when (infoData.state) {
|
return when (infoData.key.state) {
|
||||||
CoroutineInfoData.State.RUNNING -> LayeredIcon(AllIcons.Actions.Resume, Daemon_sign)
|
State.RUNNING -> LayeredIcon(AllIcons.Actions.Resume, Daemon_sign)
|
||||||
CoroutineInfoData.State.SUSPENDED -> AllIcons.Actions.Pause
|
State.SUSPENDED -> AllIcons.Actions.Pause
|
||||||
else -> EmptyIcon.create(6)
|
else -> EmptyIcon.create(6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,18 +191,18 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
|||||||
private fun getAttributes(infoData: CoroutineInfoData): SimpleTextAttributes {
|
private fun getAttributes(infoData: CoroutineInfoData): SimpleTextAttributes {
|
||||||
return when {
|
return when {
|
||||||
infoData.isSuspended() -> SimpleTextAttributes.GRAY_ATTRIBUTES
|
infoData.isSuspended() -> SimpleTextAttributes.GRAY_ATTRIBUTES
|
||||||
infoData.isEmptyStackTrace() -> SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, Color.GRAY.brighter())
|
infoData.isEmptyStack() -> SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, Color.GRAY.brighter())
|
||||||
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class CoroutineListCellRenderer : ColoredListCellRenderer<Any>() {
|
private inner class CoroutineListCellRenderer : ColoredListCellRenderer<Any>() {
|
||||||
|
|
||||||
override fun customizeCellRenderer(list: JList<*>, value: Any, index: Int, selected: Boolean, hasFocus: Boolean) {
|
override fun customizeCellRenderer(list: JList<*>, value: Any, index: Int, selected: Boolean, hasFocus: Boolean) {
|
||||||
val state = value as CoroutineInfoData
|
val infoData = value as CoroutineInfoData
|
||||||
icon = getCoroutineStateIcon(state)
|
val state = infoData.key
|
||||||
val attrs = getAttributes(state)
|
icon = fromState(state.state)
|
||||||
|
val attrs = getAttributes(infoData)
|
||||||
append(state.name + " (", attrs)
|
append(state.name + " (", attrs)
|
||||||
var detail: String? = state.state.name
|
var detail: String? = state.state.name
|
||||||
if (detail == null) {
|
if (detail == null) {
|
||||||
|
|||||||
-1
@@ -11,7 +11,6 @@ import com.intellij.xdebugger.XDebugSession
|
|||||||
import com.intellij.xdebugger.XDebugSessionListener
|
import com.intellij.xdebugger.XDebugSessionListener
|
||||||
import com.intellij.xdebugger.frame.XSuspendContext
|
import com.intellij.xdebugger.frame.XSuspendContext
|
||||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||||
import com.sun.jdi.request.EventRequest
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
|
|
||||||
class CoroutineViewDebugSessionListener(
|
class CoroutineViewDebugSessionListener(
|
||||||
|
|||||||
+23
-23
@@ -8,26 +8,21 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.view
|
|||||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||||
import com.intellij.debugger.settings.ThreadsViewSettings
|
import com.intellij.debugger.settings.ThreadsViewSettings
|
||||||
import com.intellij.icons.AllIcons
|
import com.intellij.icons.AllIcons
|
||||||
import com.intellij.ide.highlighter.JavaHighlightingColors
|
import com.intellij.icons.AllIcons.General.Information
|
||||||
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
|
|
||||||
import com.intellij.openapi.editor.HighlighterColors
|
import com.intellij.openapi.editor.HighlighterColors
|
||||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
|
||||||
import com.intellij.openapi.editor.colors.EditorColors
|
|
||||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||||
import com.intellij.openapi.editor.markup.TextAttributes
|
|
||||||
import com.intellij.ui.ColoredTextContainer
|
import com.intellij.ui.ColoredTextContainer
|
||||||
import com.intellij.ui.SimpleColoredComponent
|
import com.intellij.ui.SimpleColoredComponent
|
||||||
import com.intellij.ui.SimpleTextAttributes
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
import com.intellij.xdebugger.frame.presentation.XValuePresentation
|
import com.intellij.xdebugger.frame.presentation.XValuePresentation
|
||||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||||
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
|
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
|
||||||
import com.intellij.xdebugger.ui.DebuggerColors
|
|
||||||
import com.sun.jdi.Location
|
import com.sun.jdi.Location
|
||||||
import com.sun.jdi.ReferenceType
|
import com.sun.jdi.ReferenceType
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.State
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
import java.awt.Color
|
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
|
|
||||||
class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
|
class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
|
||||||
@@ -100,26 +95,31 @@ interface CoroutineDebuggerColors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fromState(state: State): Icon =
|
||||||
|
when (state) {
|
||||||
|
State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended
|
||||||
|
State.RUNNING -> AllIcons.Debugger.ThreadRunning
|
||||||
|
State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
|
||||||
|
else -> AllIcons.Debugger.ThreadStates.Daemon_sign
|
||||||
|
}
|
||||||
|
|
||||||
class SimpleColoredTextIconPresentationRenderer {
|
class SimpleColoredTextIconPresentationRenderer {
|
||||||
val log by logger
|
val log by logger
|
||||||
private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance()
|
private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance()
|
||||||
|
|
||||||
fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon {
|
fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon {
|
||||||
val thread = infoData.activeThread
|
val thread = infoData.activeThread
|
||||||
val name = thread?.name()?.substringBefore(" @${infoData.name}") ?: ""
|
val name = thread?.name()?.substringBefore(" @${infoData.key.name}") ?: ""
|
||||||
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
|
val threadState = if (thread != null) DebuggerUtilsEx.getThreadStatusText(thread.status()) else ""
|
||||||
|
|
||||||
val icon = when (infoData.state) {
|
val icon = fromState(infoData.key.state)
|
||||||
CoroutineInfoData.State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended
|
|
||||||
CoroutineInfoData.State.RUNNING -> AllIcons.Debugger.ThreadRunning
|
|
||||||
CoroutineInfoData.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
|
|
||||||
}
|
|
||||||
|
|
||||||
val label = SimpleColoredTextIcon(icon, true)
|
val hasChildren = infoData.stackTrace.isNotEmpty() || infoData.creationStackTrace.isNotEmpty()
|
||||||
|
val label = SimpleColoredTextIcon(icon, hasChildren)
|
||||||
label.append("\"")
|
label.append("\"")
|
||||||
label.appendValue(infoData.name)
|
label.appendValue(infoData.key.name)
|
||||||
label.append("\": ${infoData.state}")
|
label.append("\": ${infoData.key.state}")
|
||||||
if(name.isNotEmpty()) {
|
if (name.isNotEmpty()) {
|
||||||
label.append(" on thread \"")
|
label.append(" on thread \"")
|
||||||
label.appendValue(name)
|
label.appendValue(name)
|
||||||
label.append("\": $threadState")
|
label.append("\": $threadState")
|
||||||
@@ -161,7 +161,7 @@ class SimpleColoredTextIconPresentationRenderer {
|
|||||||
} else {
|
} else {
|
||||||
label.append(name.substring(dotIndex + 1))
|
label.append(name.substring(dotIndex + 1))
|
||||||
if (settings.SHOW_PACKAGE_NAME) {
|
if (settings.SHOW_PACKAGE_NAME) {
|
||||||
label.append(" (${name.substring( 0, dotIndex)})")
|
label.append(" (${name.substring(0, dotIndex)})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,15 +180,15 @@ class SimpleColoredTextIconPresentationRenderer {
|
|||||||
fun renderCreationNode(infoData: CoroutineInfoData) =
|
fun renderCreationNode(infoData: CoroutineInfoData) =
|
||||||
SimpleColoredTextIcon(
|
SimpleColoredTextIcon(
|
||||||
AllIcons.Debugger.ThreadSuspended, true,
|
AllIcons.Debugger.ThreadSuspended, true,
|
||||||
KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.frame", infoData.name)
|
KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.frame", infoData.key.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun renderErrorNode(error: String) =
|
fun renderErrorNode(error: String) =
|
||||||
SimpleColoredTextIcon(AllIcons.Actions.Lightning,false, error)
|
SimpleColoredTextIcon(AllIcons.Actions.Lightning,false, KotlinDebuggerCoroutinesBundle.message(error))
|
||||||
|
|
||||||
fun renderRoorNode(text: String) =
|
fun renderInfoNode(text: String) =
|
||||||
SimpleColoredTextIcon(null, true, text)
|
SimpleColoredTextIcon(AllIcons.General.Information, false, KotlinDebuggerCoroutinesBundle.message(text))
|
||||||
|
|
||||||
fun renderGroup(groupName: String) =
|
fun renderGroup(groupName: String) =
|
||||||
SimpleColoredTextIcon(AllIcons.Debugger.ThreadGroup,true, groupName)
|
SimpleColoredTextIcon(AllIcons.Debugger.ThreadGroup, true, groupName)
|
||||||
}
|
}
|
||||||
+44
-181
@@ -6,10 +6,8 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.view
|
package org.jetbrains.kotlin.idea.debugger.coroutine.view
|
||||||
|
|
||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.engine.JavaDebugProcess
|
|
||||||
import com.intellij.debugger.engine.JavaExecutionStack
|
import com.intellij.debugger.engine.JavaExecutionStack
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
|
||||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||||
import com.intellij.ide.CommonActionsManager
|
import com.intellij.ide.CommonActionsManager
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
@@ -19,45 +17,34 @@ import com.intellij.openapi.actionSystem.DefaultActionGroup
|
|||||||
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl
|
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.ComboBox
|
import com.intellij.openapi.ui.ComboBox
|
||||||
import com.intellij.psi.JavaPsiFacade
|
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
|
||||||
import com.intellij.ui.CaptionPanel
|
import com.intellij.ui.CaptionPanel
|
||||||
import com.intellij.ui.ComboboxSpeedSearch
|
import com.intellij.ui.ComboboxSpeedSearch
|
||||||
import com.intellij.ui.DoubleClickListener
|
|
||||||
import com.intellij.ui.border.CustomLineBorder
|
import com.intellij.ui.border.CustomLineBorder
|
||||||
import com.intellij.ui.components.panels.Wrapper
|
import com.intellij.ui.components.panels.Wrapper
|
||||||
import com.intellij.util.SingleAlarm
|
import com.intellij.util.SingleAlarm
|
||||||
import com.intellij.xdebugger.XDebugSession
|
import com.intellij.xdebugger.XDebugSession
|
||||||
import com.intellij.xdebugger.XDebuggerUtil
|
|
||||||
import com.intellij.xdebugger.XSourcePosition
|
|
||||||
import com.intellij.xdebugger.frame.*
|
import com.intellij.xdebugger.frame.*
|
||||||
import com.intellij.xdebugger.impl.actions.XDebuggerActions
|
import com.intellij.xdebugger.impl.actions.XDebuggerActions
|
||||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
|
|
||||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel
|
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel
|
||||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeRestorer
|
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeRestorer
|
||||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState
|
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState
|
||||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode
|
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode
|
||||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
|
|
||||||
import com.sun.jdi.request.EventRequest
|
import com.sun.jdi.request.EventRequest
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo
|
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.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider
|
import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CreationCoroutineStackFrameItem
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.LookupContinuation
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ManagerThreadExecutor
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ManagerThreadExecutor
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParams
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParams
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParamsProvider
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParamsProvider
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.XDebugSessionListenerProvider
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.XDebugSessionListenerProvider
|
||||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.event.KeyAdapter
|
|
||||||
import java.awt.event.KeyEvent
|
|
||||||
import java.awt.event.MouseEvent
|
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
|
||||||
@@ -70,21 +57,20 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
val someCombobox = ComboBox<String>()
|
val someCombobox = ComboBox<String>()
|
||||||
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
|
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
|
||||||
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
|
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
|
||||||
val javaDebugProcess = session.debugProcess as JavaDebugProcess
|
// val javaDebugProcess =
|
||||||
val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
|
// val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
|
||||||
val renderer = SimpleColoredTextIconPresentationRenderer()
|
val renderer = SimpleColoredTextIconPresentationRenderer()
|
||||||
val managerThreadExecutor = ManagerThreadExecutor(debugProcess)
|
val managerThreadExecutor = ManagerThreadExecutor(session)
|
||||||
val applicationThreadExecutor = ApplicationThreadExecutor()
|
|
||||||
var treeState: XDebuggerTreeState? = null
|
var treeState: XDebuggerTreeState? = null
|
||||||
private var restorer: XDebuggerTreeRestorer? = null
|
private var restorer: XDebuggerTreeRestorer? = null
|
||||||
private var selectedNodeListener = XDebuggerTreeSelectedNodeListener(panel.tree)
|
private var selectedNodeListener: XDebuggerTreeSelectedNodeListener? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val VIEW_CLEAR_DELAY = 100 //ms
|
private val VIEW_CLEAR_DELAY = 100 //ms
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
someCombobox.setRenderer(versionedImplementationProvider.comboboxListCellRenderer())
|
someCombobox.renderer = versionedImplementationProvider.comboboxListCellRenderer()
|
||||||
object : ComboboxSpeedSearch(someCombobox) {
|
object : ComboboxSpeedSearch(someCombobox) {
|
||||||
override fun getElementText(element: Any?): String? {
|
override fun getElementText(element: Any?): String? {
|
||||||
return element.toString()
|
return element.toString()
|
||||||
@@ -93,14 +79,14 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
someCombobox.addItem(null)
|
someCombobox.addItem(null)
|
||||||
val myToolbar = createToolbar()
|
val myToolbar = createToolbar()
|
||||||
val myThreadsPanel = Wrapper()
|
val myThreadsPanel = Wrapper()
|
||||||
myThreadsPanel.setBorder(CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0))
|
myThreadsPanel.border = CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0)
|
||||||
myThreadsPanel.add(myToolbar?.getComponent(), BorderLayout.EAST)
|
myThreadsPanel.add(myToolbar?.component, BorderLayout.EAST)
|
||||||
myThreadsPanel.add(someCombobox, BorderLayout.CENTER)
|
myThreadsPanel.add(someCombobox, BorderLayout.CENTER)
|
||||||
mainPanel.add(panel.mainPanel, BorderLayout.CENTER)
|
mainPanel.add(panel.mainPanel, BorderLayout.CENTER)
|
||||||
selectedNodeListener.installOn()
|
selectedNodeListener = XDebuggerTreeSelectedNodeListener(session, panel.tree)
|
||||||
|
selectedNodeListener?.installOn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun createToolbar(): ActionToolbarImpl? {
|
private fun createToolbar(): ActionToolbarImpl? {
|
||||||
val framesGroup = DefaultActionGroup()
|
val framesGroup = DefaultActionGroup()
|
||||||
val actionsManager = CommonActionsManager.getInstance()
|
val actionsManager = CommonActionsManager.getInstance()
|
||||||
@@ -127,7 +113,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renewRoot(suspendContext: XSuspendContext) {
|
fun renewRoot(suspendContext: SuspendContextImpl) {
|
||||||
panel.tree.setRoot(XCoroutinesRootNode(suspendContext), false)
|
panel.tree.setRoot(XCoroutinesRootNode(suspendContext), false)
|
||||||
if (treeState != null) {
|
if (treeState != null) {
|
||||||
restorer?.dispose()
|
restorer?.dispose()
|
||||||
@@ -136,7 +122,10 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
restorer?.dispose()
|
if (restorer != null) {
|
||||||
|
restorer?.dispose()
|
||||||
|
restorer = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forceClear() {
|
fun forceClear() {
|
||||||
@@ -157,15 +146,15 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
|
|
||||||
inner class EmptyNode : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {})
|
inner class EmptyNode : XValueContainerNode<XValueContainer>(panel.tree, null, true, object : XValueContainer() {})
|
||||||
|
|
||||||
inner class XCoroutinesRootNode(suspendContext: XSuspendContext) :
|
inner class XCoroutinesRootNode(suspendContext: SuspendContextImpl) :
|
||||||
XValueContainerNode<CoroutineGroupContainer>(
|
XValueContainerNode<CoroutineGroupContainer>(
|
||||||
panel.tree, null, false,
|
panel.tree, null, false,
|
||||||
CoroutineGroupContainer(suspendContext, KotlinDebuggerCoroutinesBundle.message("coroutine.view.default.group"))
|
CoroutineGroupContainer(suspendContext, KotlinDebuggerCoroutinesBundle.message("coroutine.view.default.group"))
|
||||||
)
|
)
|
||||||
|
|
||||||
inner class CoroutineGroupContainer(val suspendContext: XSuspendContext, val groupName: String) : XValueContainer() {
|
inner class CoroutineGroupContainer(val suspendContext: SuspendContextImpl, val groupName: String) : XValueContainer() {
|
||||||
override fun computeChildren(node: XCompositeNode) {
|
override fun computeChildren(node: XCompositeNode) {
|
||||||
if (suspendContext is SuspendContextImpl && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||||
val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
|
val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
|
||||||
node.addChildren(groups, true)
|
node.addChildren(groups, true)
|
||||||
} else {
|
} else {
|
||||||
@@ -178,7 +167,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
inner class CoroutineContainer(
|
inner class CoroutineContainer(
|
||||||
val suspendContext: XSuspendContext,
|
val suspendContext: SuspendContextImpl,
|
||||||
val groupName: String
|
val groupName: String
|
||||||
) : RendererContainer(renderer.renderGroup(groupName)) {
|
) : RendererContainer(renderer.renderGroup(groupName)) {
|
||||||
|
|
||||||
@@ -189,38 +178,44 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
var coroutineCache = debugProbesProxy.dumpCoroutines()
|
var coroutineCache = debugProbesProxy.dumpCoroutines()
|
||||||
if (coroutineCache.isOk()) {
|
if (coroutineCache.isOk()) {
|
||||||
val children = XValueChildrenList()
|
val children = XValueChildrenList()
|
||||||
coroutineCache.cache.forEach {
|
for (coroutineInfo in coroutineCache.cache) {
|
||||||
children.add(FramesContainer(it, suspendContext))
|
children.add(FramesContainer(coroutineInfo, suspendContext))
|
||||||
}
|
}
|
||||||
node.addChildren(children, true)
|
if (children.size() > 0)
|
||||||
|
node.addChildren(children, true)
|
||||||
|
else
|
||||||
|
node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true)
|
||||||
} else {
|
} else {
|
||||||
val errorNode = ErrorNode(KotlinDebuggerCoroutinesBundle.message("coroutine.view.fetching.error"))
|
val errorNode = ErrorNode("coroutine.view.fetching.error")
|
||||||
node.addChildren(XValueChildrenList.singleton(errorNode), true)
|
node.addChildren(XValueChildrenList.singleton(errorNode), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class InfoNode(val error: String) : RendererContainer(renderer.renderInfoNode(error))
|
||||||
|
|
||||||
inner class ErrorNode(val error: String) : RendererContainer(renderer.renderErrorNode(error))
|
inner class ErrorNode(val error: String) : RendererContainer(renderer.renderErrorNode(error))
|
||||||
|
|
||||||
inner class FramesContainer(
|
inner class FramesContainer(
|
||||||
private val infoData: CoroutineInfoData,
|
private val infoData: CoroutineInfoData,
|
||||||
private val suspendContext: XSuspendContext
|
private val suspendContext: SuspendContextImpl
|
||||||
) : RendererContainer(renderer.render(infoData)) {
|
) : RendererContainer(renderer.render(infoData)) {
|
||||||
|
|
||||||
override fun computeChildren(node: XCompositeNode) {
|
override fun computeChildren(node: XCompositeNode) {
|
||||||
managerThreadExecutor.on(suspendContext).schedule {
|
managerThreadExecutor.on(suspendContext).schedule {
|
||||||
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
|
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
|
||||||
val children = XValueChildrenList()
|
val children = XValueChildrenList()
|
||||||
debugProbesProxy.frameBuilder().build(infoData)
|
var stackFrames = debugProbesProxy.frameBuilder().build(infoData)
|
||||||
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
|
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
|
||||||
infoData.stackFrameList.forEach {
|
for (frame in stackFrames) {
|
||||||
if (it is CreationCoroutineStackFrameItem)
|
if (frame is CreationCoroutineStackFrameItem)
|
||||||
creationStack.add(it)
|
creationStack.add(frame)
|
||||||
else if (it is CoroutineStackFrameItem)
|
else if (frame is CoroutineStackFrameItem)
|
||||||
children.add(CoroutineFrameValue(it))
|
children.add(CoroutineFrameValue(infoData, frame))
|
||||||
}
|
}
|
||||||
children.add(CreationFramesContainer(infoData, creationStack))
|
if (creationStack.isNotEmpty())
|
||||||
|
children.add(CreationFramesContainer(infoData, creationStack))
|
||||||
node.addChildren(children, true)
|
node.addChildren(children, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,17 +230,18 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
val children = XValueChildrenList()
|
val children = XValueChildrenList()
|
||||||
|
|
||||||
creationFrames.forEach {
|
creationFrames.forEach {
|
||||||
children.add(CoroutineFrameValue(it))
|
children.add(CoroutineFrameValue(infoData, it))
|
||||||
}
|
}
|
||||||
node.addChildren(children, true)
|
node.addChildren(children, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class CoroutineFrameValue(
|
inner class CoroutineFrameValue(
|
||||||
val frame: CoroutineStackFrameItem
|
val infoData: CoroutineInfoData,
|
||||||
) : XNamedValue(frame.uniqueId()) {
|
val frameItem: CoroutineStackFrameItem
|
||||||
|
) : XNamedValue(frameItem.uniqueId()) {
|
||||||
override fun computePresentation(node: XValueNode, place: XValuePlace) =
|
override fun computePresentation(node: XValueNode, place: XValuePlace) =
|
||||||
applyRenderer(node, renderer.render(frame.location))
|
applyRenderer(node, renderer.render(frameItem.location))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyRenderer(node: XValueNode, presentation: SimpleColoredTextIcon) =
|
private fun applyRenderer(node: XValueNode, presentation: SimpleColoredTextIcon) =
|
||||||
@@ -256,137 +252,4 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
|||||||
applyRenderer(node, presentation)
|
applyRenderer(node, presentation)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class KeyMouseEvent(val keyEvent: KeyEvent?, val mouseEvent: MouseEvent?) {
|
|
||||||
constructor(keyEvent: KeyEvent) : this(keyEvent, null)
|
|
||||||
constructor(mouseEvent: MouseEvent) : this(null, mouseEvent)
|
|
||||||
|
|
||||||
fun isKeyEvent() = keyEvent != null
|
|
||||||
|
|
||||||
fun isMouseEvent() = mouseEvent != null
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class XDebuggerTreeSelectedNodeListener(val tree: XDebuggerTree) {
|
|
||||||
|
|
||||||
fun installOn() {
|
|
||||||
object : DoubleClickListener() {
|
|
||||||
override fun onDoubleClick(e: MouseEvent) =
|
|
||||||
nodeSelected(KeyMouseEvent(e))
|
|
||||||
}.installOn(tree)
|
|
||||||
|
|
||||||
tree.addKeyListener(
|
|
||||||
object : KeyAdapter() {
|
|
||||||
override fun keyPressed(e: KeyEvent) {
|
|
||||||
val key = e.keyCode
|
|
||||||
if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT)
|
|
||||||
nodeSelected(KeyMouseEvent(e))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun nodeSelected(event: KeyMouseEvent): Boolean {
|
|
||||||
val selectedNodes = tree.getSelectedNodes(XValueNodeImpl::class.java, null)
|
|
||||||
if (selectedNodes.size == 1) {
|
|
||||||
val node = selectedNodes[0]
|
|
||||||
val valueContainer = node.valueContainer
|
|
||||||
if (valueContainer is XCoroutineView.CoroutineFrameValue) {
|
|
||||||
val frame = valueContainer.frame
|
|
||||||
val threadSuspendContext = session.suspendContext as SuspendContextImpl
|
|
||||||
when (frame) {
|
|
||||||
is RunningCoroutineStackFrameItem -> {
|
|
||||||
val threadProxy = frame.frame.threadProxy()
|
|
||||||
val isCurrentContext = threadSuspendContext.thread == threadProxy
|
|
||||||
createStackAndSetFrame(threadProxy, { frame.stackFrame }, isCurrentContext)
|
|
||||||
}
|
|
||||||
is CreationCoroutineStackFrameItem -> {
|
|
||||||
val position =
|
|
||||||
getPosition(frame.stackTraceElement.className, frame.stackTraceElement.lineNumber) ?: return false
|
|
||||||
val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
|
|
||||||
createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) })
|
|
||||||
}
|
|
||||||
is SuspendCoroutineStackFrameItem -> {
|
|
||||||
val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
|
|
||||||
val executionContext = executionContext(threadSuspendContext, frame.frame)
|
|
||||||
createStackAndSetFrame(threadProxy, { createSyntheticStackFrame(executionContext, frame) })
|
|
||||||
}
|
|
||||||
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 -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createStackAndSetFrame(
|
|
||||||
threadReferenceProxy: ThreadReferenceProxyImpl,
|
|
||||||
stackFrameProvider: () -> XStackFrame?,
|
|
||||||
isCurrentContext: Boolean = false
|
|
||||||
) {
|
|
||||||
val threadSuspendContext = session.suspendContext as SuspendContextImpl
|
|
||||||
managerThreadExecutor.on(threadSuspendContext).schedule {
|
|
||||||
val stackFrame = stackFrameProvider.invoke()
|
|
||||||
if (stackFrame is XStackFrame) {
|
|
||||||
val executionStack = createExecutionStack(threadReferenceProxy, isCurrentContext)
|
|
||||||
applicationThreadExecutor.schedule(
|
|
||||||
{
|
|
||||||
session.setCurrentStackFrame(executionStack, stackFrame)
|
|
||||||
},
|
|
||||||
panel.tree
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false): XExecutionStack {
|
|
||||||
val executionStack = CoroutineDebuggerExecutionStack(proxy, isCurrentContext)
|
|
||||||
executionStack.initTopFrame()
|
|
||||||
return executionStack
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class CoroutineDebuggerExecutionStack(threadReferenceProxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean) :
|
|
||||||
JavaExecutionStack(threadReferenceProxy, debugProcess, isCurrentContext)
|
|
||||||
|
|
||||||
|
|
||||||
private fun getPosition(className: String, lineNumber: Int): XSourcePosition? {
|
|
||||||
val psiFacade = JavaPsiFacade.getInstance(project)
|
|
||||||
val psiClass = psiFacade.findClass(
|
|
||||||
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 (lineNumber > 0) lineNumber - 1 else return null
|
|
||||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun executionContext(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl): DefaultExecutionContext {
|
|
||||||
return DefaultExecutionContext(suspendContext, frameProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSyntheticStackFrame(
|
|
||||||
executionContext: DefaultExecutionContext,
|
|
||||||
frame: SuspendCoroutineStackFrameItem
|
|
||||||
): SyntheticStackFrame? {
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
val asyncStackTraceContext = lookupContinuation.createAsyncStackTraceContext(continuation)
|
|
||||||
val vars = asyncStackTraceContext?.getSpilledVariables(continuation) ?: return null
|
|
||||||
return SyntheticStackFrame(frame.emptyDescriptor(), vars, position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.view
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.engine.JavaDebugProcess
|
||||||
|
import com.intellij.debugger.engine.JavaExecutionStack
|
||||||
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
|
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||||
|
import com.intellij.ui.DoubleClickListener
|
||||||
|
import com.intellij.xdebugger.XDebugSession
|
||||||
|
import com.intellij.xdebugger.frame.XExecutionStack
|
||||||
|
import com.intellij.xdebugger.frame.XStackFrame
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
|
||||||
|
import com.sun.jdi.ObjectReference
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.findPosition
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.suspendContextImpl
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
||||||
|
import java.awt.event.KeyAdapter
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
|
||||||
|
class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XDebuggerTree) {
|
||||||
|
val applicationThreadExecutor = ApplicationThreadExecutor()
|
||||||
|
val javaDebugProcess = session.debugProcess as JavaDebugProcess
|
||||||
|
val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
|
||||||
|
|
||||||
|
fun installOn() {
|
||||||
|
object : DoubleClickListener() {
|
||||||
|
override fun onDoubleClick(e: MouseEvent) =
|
||||||
|
nodeSelected(KeyMouseEvent(e))
|
||||||
|
}.installOn(tree)
|
||||||
|
|
||||||
|
tree.addKeyListener(
|
||||||
|
object : KeyAdapter() {
|
||||||
|
override fun keyPressed(e: KeyEvent) {
|
||||||
|
val key = e.keyCode
|
||||||
|
if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT)
|
||||||
|
nodeSelected(KeyMouseEvent(e))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nodeSelected(event: KeyMouseEvent): Boolean {
|
||||||
|
val selectedNodes = tree.getSelectedNodes(XValueNodeImpl::class.java, null)
|
||||||
|
if (selectedNodes.size == 1) {
|
||||||
|
val node = selectedNodes[0]
|
||||||
|
val valueContainer = node.valueContainer
|
||||||
|
val suspendContext = session.suspendContextImpl()
|
||||||
|
if (valueContainer is XCoroutineView.CoroutineFrameValue) {
|
||||||
|
when (val stackFrameItem = valueContainer.frameItem) {
|
||||||
|
is RunningCoroutineStackFrameItem -> {
|
||||||
|
val threadProxy = stackFrameItem.frame.threadProxy()
|
||||||
|
val isCurrentContext = suspendContext.thread == threadProxy
|
||||||
|
val executionStack = JavaExecutionStack(
|
||||||
|
threadProxy,
|
||||||
|
debugProcess,
|
||||||
|
isCurrentContext
|
||||||
|
)
|
||||||
|
val jStackFrame = executionStack.createStackFrame(stackFrameItem.frame)
|
||||||
|
createStackAndSetFrame(threadProxy, { jStackFrame }, isCurrentContext)
|
||||||
|
}
|
||||||
|
is CreationCoroutineStackFrameItem -> {
|
||||||
|
val position = stackFrameItem.stackTraceElement.findPosition(session.project) ?: return false
|
||||||
|
val threadProxy = suspendContext.thread ?: return false
|
||||||
|
val realFrame = threadProxy.forceFrames().first() ?: return false
|
||||||
|
createStackAndSetFrame(threadProxy, {
|
||||||
|
SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), emptyList(), position)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
is SuspendCoroutineStackFrameItem -> {
|
||||||
|
val threadProxy = suspendContext.thread ?: return false
|
||||||
|
val realFrame = threadProxy.forceFrames().first() ?: return false
|
||||||
|
val lastFrame = valueContainer.infoData.lastObservedFrameFieldRef ?: return false
|
||||||
|
createStackAndSetFrame(threadProxy, { createSyntheticStackFrame(suspendContext, stackFrameItem, realFrame, lastFrame) })
|
||||||
|
}
|
||||||
|
is RestoredCoroutineStackFrameItem -> {
|
||||||
|
val threadProxy = stackFrameItem.frame.threadProxy()
|
||||||
|
val position = stackFrameItem.location.findPosition(session.project)
|
||||||
|
?: return false
|
||||||
|
createStackAndSetFrame(threadProxy, {
|
||||||
|
SyntheticStackFrame(stackFrameItem.emptyDescriptor(), stackFrameItem.spilledVariables, position)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
is DefaultCoroutineStackFrameItem -> {
|
||||||
|
val threadProxy = suspendContext.thread ?: return false
|
||||||
|
val position = stackFrameItem.location.findPosition(session.project)
|
||||||
|
?: return false
|
||||||
|
val realFrame = threadProxy.forceFrames().first() ?: return false
|
||||||
|
createStackAndSetFrame(threadProxy, {
|
||||||
|
SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), stackFrameItem.spilledVariables, position)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createStackAndSetFrame(
|
||||||
|
threadReferenceProxy: ThreadReferenceProxyImpl,
|
||||||
|
stackFrameProvider: () -> XStackFrame?,
|
||||||
|
isCurrentContext: Boolean = false
|
||||||
|
) {
|
||||||
|
val stackFrameStack = debugProcess.invokeInManagerThread {
|
||||||
|
val stackFrame = stackFrameProvider.invoke() ?: return@invokeInManagerThread null
|
||||||
|
XStackFrameStack(stackFrame, createExecutionStack(threadReferenceProxy, isCurrentContext))
|
||||||
|
} ?: return
|
||||||
|
setCurrentStackFrame(stackFrameStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentStackFrame(stackFrameStack: XStackFrameStack) {
|
||||||
|
applicationThreadExecutor.schedule(
|
||||||
|
{
|
||||||
|
session.setCurrentStackFrame(stackFrameStack.executionStack, stackFrameStack.stackFrame)
|
||||||
|
}, tree
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class XStackFrameStack(val stackFrame: XStackFrame, val executionStack: XExecutionStack);
|
||||||
|
|
||||||
|
private fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false): XExecutionStack {
|
||||||
|
val executionStack = JavaExecutionStack(proxy, debugProcess, isCurrentContext)
|
||||||
|
executionStack.initTopFrame()
|
||||||
|
return executionStack
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createSyntheticStackFrame(
|
||||||
|
suspendContext: SuspendContextImpl,
|
||||||
|
frame: SuspendCoroutineStackFrameItem,
|
||||||
|
topFrame: StackFrameProxyImpl,
|
||||||
|
initialContinuation: ObjectReference
|
||||||
|
): SyntheticStackFrame? {
|
||||||
|
val position =
|
||||||
|
applicationThreadExecutor.readAction { frame.stackTraceElement.findPosition(session.project) }
|
||||||
|
?: return null
|
||||||
|
val continuation =
|
||||||
|
ContinuationHolder.lookup(suspendContext, initialContinuation)
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
return SyntheticStackFrame(
|
||||||
|
frame.emptyDescriptor(topFrame),
|
||||||
|
continuation.getSpilledVariables() ?: return null,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class KeyMouseEvent(val keyEvent: KeyEvent?, val mouseEvent: MouseEvent?) {
|
||||||
|
constructor(keyEvent: KeyEvent) : this(keyEvent, null)
|
||||||
|
constructor(mouseEvent: MouseEvent) : this(null, mouseEvent)
|
||||||
|
|
||||||
|
fun isKeyEvent() = keyEvent != null
|
||||||
|
|
||||||
|
fun isMouseEvent() = mouseEvent != null
|
||||||
|
}
|
||||||
+2
-1
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.test
|
package org.jetbrains.kotlin.idea.debugger.test
|
||||||
|
|
||||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||||
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
import com.intellij.debugger.engine.JavaValue
|
import com.intellij.debugger.engine.JavaValue
|
||||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import com.intellij.execution.process.ProcessOutputTypes
|
import com.intellij.execution.process.ProcessOutputTypes
|
||||||
@@ -37,7 +38,7 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
|
|||||||
val frameProxy = this.frameProxy
|
val frameProxy = this.frameProxy
|
||||||
if (frameProxy != null) {
|
if (frameProxy != null) {
|
||||||
try {
|
try {
|
||||||
val stackTrace = asyncStackTraceProvider.getAsyncStackTraceSafe(frameProxy, this)
|
val stackTrace = asyncStackTraceProvider.lookupForResumeContinuation(frameProxy, this)
|
||||||
if (stackTrace != null && stackTrace.isNotEmpty()) {
|
if (stackTrace != null && stackTrace.isNotEmpty()) {
|
||||||
print(renderAsyncStackTrace(stackTrace), ProcessOutputTypes.SYSTEM)
|
print(renderAsyncStackTrace(stackTrace), ProcessOutputTypes.SYSTEM)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ abstract class AbstractCoroutineDumpTest : KotlinDescriptorTestCaseWithStepping(
|
|||||||
|
|
||||||
private fun stringDump(infoData: List<CoroutineInfoData>) = buildString {
|
private fun stringDump(infoData: List<CoroutineInfoData>) = buildString {
|
||||||
infoData.forEach {
|
infoData.forEach {
|
||||||
appendln("\"${it.name}\", state: ${it.state}")
|
appendln("\"${it.key.name}\", state: ${it.key.state}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -194,6 +194,7 @@ sealed class BaseExecutionContext(val evaluationContext: EvaluationContextImpl)
|
|||||||
|
|
||||||
fun findAndInvoke(instance: ObjectReference, name: String, methodSignature: String? = null, vararg params: Value): Value? {
|
fun findAndInvoke(instance: ObjectReference, name: String, methodSignature: String? = null, vararg params: Value): Value? {
|
||||||
val type = instance.referenceType()
|
val type = instance.referenceType()
|
||||||
|
type.allMethods()
|
||||||
val method =
|
val method =
|
||||||
if (methodSignature is String)
|
if (methodSignature is String)
|
||||||
type.methodsByName(name, methodSignature).single()
|
type.methodsByName(name, methodSignature).single()
|
||||||
|
|||||||
@@ -189,10 +189,22 @@
|
|||||||
description="Enable bytecode instrumentation for Kotlin classes"
|
description="Enable bytecode instrumentation for Kotlin classes"
|
||||||
defaultValue="false"
|
defaultValue="false"
|
||||||
restartRequired="false"/>
|
restartRequired="false"/>
|
||||||
<registryKey key="kotlin.debugger.coroutines"
|
<registryKey key="kotlin.debugger.coroutines.standalone"
|
||||||
description="Enable debugging for coroutines in Kotlin/JVM"
|
description="Enable debugging for coroutines in Kotlin/JVM with no library support"
|
||||||
defaultValue="false"
|
defaultValue="false"
|
||||||
restartRequired="false"/>
|
restartRequired="false"/>
|
||||||
|
<registryKey key="kotlin.debugger.coroutines.trace"
|
||||||
|
description="Trace coroutines debugger output"
|
||||||
|
defaultValue="false"
|
||||||
|
restartRequired="false"/>
|
||||||
|
<registryKey key="kotlin.debugger.coroutines.max"
|
||||||
|
description="Maximum amount of coroutines for debug"
|
||||||
|
defaultValue="1000"
|
||||||
|
restartRequired="false"/>
|
||||||
|
<registryKey key="kotlin.debugger.coroutines.switch"
|
||||||
|
description="Continuation information provider possible values are: DISPATCHED_CONTINUATION, CANCELLABLE_CONTINUATION."
|
||||||
|
defaultValue="DISPATCHED_CONTINUATION"
|
||||||
|
restartRequired="false"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.uast">
|
<extensions defaultExtensionNs="org.jetbrains.uast">
|
||||||
|
|||||||
@@ -380,6 +380,9 @@
|
|||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.debugger.StackFrameInterceptor"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.debugger.StackFrameInterceptor"
|
||||||
serviceImplementation="org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineStackFrameInterceptor"/>
|
serviceImplementation="org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineStackFrameInterceptor"/>
|
||||||
|
|
||||||
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.debugger.coroutine.DebuggerListener"
|
||||||
|
serviceImplementation="org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerListener"/>
|
||||||
|
|
||||||
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
|
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
|
||||||
|
|
||||||
<registryKey
|
<registryKey
|
||||||
|
|||||||
@@ -57,3 +57,7 @@ org.jetbrains.kotlin.util.KotlinVersionsTest.testVersionsAreConsistent, KT-35567
|
|||||||
org.jetbrains.kotlinx.serialization.SerializationIrBytecodeListingTestGenerated.testBasic, wait until new serialziation runtime version will be publicly available
|
org.jetbrains.kotlinx.serialization.SerializationIrBytecodeListingTestGenerated.testBasic, wait until new serialziation runtime version will be publicly available
|
||||||
org.jetbrains.kotlin.jvm.compiler.CompileKotlinAgainstJavaTestGenerated.WithAPT.testClassWithTypeParameter, KT-36448
|
org.jetbrains.kotlin.jvm.compiler.CompileKotlinAgainstJavaTestGenerated.WithAPT.testClassWithTypeParameter, KT-36448
|
||||||
org.jetbrains.kotlin.jvm.compiler.CompileKotlinAgainstJavaTestGenerated.WithoutAPT.testClassWithTypeParameter, KT-36448
|
org.jetbrains.kotlin.jvm.compiler.CompileKotlinAgainstJavaTestGenerated.WithoutAPT.testClassWithTypeParameter, KT-36448
|
||||||
|
org.jetbrains.kotlin.idea.debugger.test.AsyncStackTraceTestGenerated.testAllFilesPresentInAsyncStackTrace, redesign test AsyncStackTraces
|
||||||
|
org.jetbrains.kotlin.idea.debugger.test.AsyncStackTraceTestGenerated.testAsyncFunctions, redesign test AsyncStackTraces
|
||||||
|
org.jetbrains.kotlin.idea.debugger.test.AsyncStackTraceTestGenerated.testAsyncLambdas, redesign test AsyncStackTraces
|
||||||
|
org.jetbrains.kotlin.idea.debugger.test.AsyncStackTraceTestGenerated.testAsyncSimple, redesign test AsyncStackTraces
|
||||||
|
|||||||
|
Can't render this file because it contains an unexpected character in line 12 and column 132.
|
Reference in New Issue
Block a user