(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
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.SystemProperties
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.standaloneCoroutineDebuggerEnabled
|
||||
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
|
||||
|
||||
class KotlinGradleCoroutineDebugProjectResolver : AbstractProjectResolverExtension() {
|
||||
@@ -17,10 +17,9 @@ class KotlinGradleCoroutineDebugProjectResolver : AbstractProjectResolverExtensi
|
||||
|
||||
override fun enhanceTaskProcessing(taskNames: MutableList<String>, jvmParametersSetup: String?, initScriptConsumer: Consumer<String>) {
|
||||
try {
|
||||
if (coroutineDebuggerEnabled())
|
||||
setupCoroutineAgentForJvmForkedTestTasks(initScriptConsumer)
|
||||
setupCoroutineAgentForJvmForkedTestTasks(initScriptConsumer)
|
||||
} 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())
|
||||
initScriptConsumer.consume(script)
|
||||
}
|
||||
|
||||
// supposed to be the same as [CoroutineProjectConnectionListener.kt].coroutineDebuggerEnabled
|
||||
private fun coroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines")
|
||||
}
|
||||
+2
-1
@@ -22,4 +22,5 @@ coroutine.dump.threads.loading=Loading…
|
||||
|
||||
coroutine.view.title=Coroutines
|
||||
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
|
||||
|
||||
import com.intellij.debugger.engine.*
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.*
|
||||
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.proxy.AsyncStackTraceContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.BaseExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.hopelessAware
|
||||
import org.jetbrains.kotlin.idea.debugger.isInKotlinSources
|
||||
|
||||
class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<CoroutineStackFrameItem>? =
|
||||
getAsyncStackTrace(stackFrame, suspendContext as XSuspendContext)
|
||||
|
||||
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineStackFrameItem>? {
|
||||
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) }
|
||||
return if (stackFrameList == null || stackFrameList.isEmpty()) null else stackFrameList
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
val stackFrameList = hopelessAware {
|
||||
if (stackFrame is CoroutinePreflightStackFrame)
|
||||
lookupForAfterPreflight(stackFrame, suspendContext)
|
||||
else
|
||||
null
|
||||
}
|
||||
return if (stackFrameList == null || stackFrameList.isEmpty())
|
||||
null
|
||||
else stackFrameList
|
||||
}
|
||||
|
||||
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineStackFrameItem> {
|
||||
val defaultResult = emptyList<CoroutineStackFrameItem>()
|
||||
fun lookupForAfterPreflight(
|
||||
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()
|
||||
if (!location.isInKotlinSources())
|
||||
return defaultResult
|
||||
return null
|
||||
|
||||
val method = location.safeMethod() ?: return defaultResult
|
||||
val threadReference = frameProxy.threadProxy().threadReference
|
||||
|
||||
if (threadReference == null || !threadReference.isSuspended || !canRunEvaluation(suspendContext))
|
||||
return defaultResult
|
||||
|
||||
val context = DefaultExecutionContext(suspendContext as SuspendContextImpl, frameProxy)
|
||||
val astContext = AsyncStackTraceContext(context, method)
|
||||
return astContext.getAsyncStackTraceIfAny()
|
||||
if (threadAndContextSupportsEvaluation(suspendContext, frameProxy))
|
||||
return ContinuationHolder.lookupForResumeMethodContinuation(suspendContext, frameProxy)
|
||||
?.getAsyncStackTraceIfAny()?.stackFrameItems
|
||||
return null
|
||||
}
|
||||
|
||||
fun canRunEvaluation(suspendContext: XSuspendContext) =
|
||||
(suspendContext as SuspendContextImpl).debugProcess.canRunEvaluation
|
||||
private fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl) =
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
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.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.idea.debugger.StackFrameInterceptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||
|
||||
class CoroutineStackFrameInterceptor(val project: Project) : StackFrameInterceptor {
|
||||
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
|
||||
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.SimpleListCellRenderer
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import javax.swing.ListCellRenderer
|
||||
|
||||
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.ThreadReferenceProxyImpl
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
||||
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.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
|
||||
|
||||
class CoroutineBuilder(val suspendContext: XSuspendContext) {
|
||||
class CoroutineBuilder(val suspendContext: SuspendContextImpl) {
|
||||
private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
|
||||
val debugProcess = (suspendContext as SuspendContextImpl).debugProcess
|
||||
val debugProcess = suspendContext.debugProcess
|
||||
private val virtualMachineProxy = debugProcess.virtualMachineProxy
|
||||
private val classesByName = ClassesByNameProvider.createCache(virtualMachineProxy.allClasses())
|
||||
|
||||
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> {
|
||||
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
||||
val firstSuspendedStackFrameProxyImpl = firstSuspendedThreadFrame()
|
||||
val creationFrameSeparatorIndex = findCreationFrameIndex(coroutine.stackTrace)
|
||||
|
||||
if (coroutine.state == CoroutineInfoData.State.RUNNING && coroutine.activeThread is ThreadReference) {
|
||||
val threadReferenceProxyImpl = runningThreadProxy(coroutine.activeThread)
|
||||
val executionStack = JavaExecutionStack(threadReferenceProxyImpl, debugProcess, suspendedSameThread(coroutine.activeThread))
|
||||
if (coroutine.isRunning() && coroutine.activeThread is ThreadReference) {
|
||||
val threadReferenceProxyImpl = ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, coroutine.activeThread)
|
||||
|
||||
val frames = threadReferenceProxyImpl.forceFrames()
|
||||
val realFrames = threadReferenceProxyImpl.forceFrames()
|
||||
var coroutineStackInserted = false
|
||||
for (runningStackFrameProxy in frames) {
|
||||
val jStackFrame = executionStack.createStackFrame(runningStackFrameProxy)
|
||||
val coroutineStack = coroutineStackFrameProvider.getAsyncStackTraceSafe(runningStackFrameProxy, suspendContext)
|
||||
if (coroutineStack.isNotEmpty()) {
|
||||
// clue coroutine stack into the thread's real stack
|
||||
|
||||
val firstMergedFrame = mergeFrameVars(coroutineStack.first(), runningStackFrameProxy, jStackFrame)
|
||||
coroutineStackFrameList.add(firstMergedFrame)
|
||||
|
||||
for (asyncFrame in coroutineStack.drop(1)) {
|
||||
coroutineStackFrameList.add(
|
||||
RestoredCoroutineStackFrameItem(
|
||||
runningStackFrameProxy,
|
||||
asyncFrame.location,
|
||||
asyncFrame.spilledVariables
|
||||
)
|
||||
)
|
||||
coroutineStackInserted = true
|
||||
}
|
||||
} else {
|
||||
if (coroutineStackInserted && isInvokeSuspendNegativeLineMethodFrame(runningStackFrameProxy)) {
|
||||
coroutineStackInserted = false
|
||||
} else
|
||||
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, jStackFrame))
|
||||
var preflightFound = false
|
||||
for (runningStackFrameProxy in realFrames) {
|
||||
if (runningStackFrameProxy.location().isPreFlight()) {
|
||||
preflightFound = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else if ((coroutine.state == CoroutineInfoData.State
|
||||
.SUSPENDED || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference
|
||||
) {
|
||||
// to get frames from CoroutineInfo anyway
|
||||
// the thread is paused on breakpoint - it has at least one frame
|
||||
val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex)
|
||||
for (suspendedFrame in suspendedStackTrace) {
|
||||
val location = createLocation(suspendedFrame)
|
||||
coroutineStackFrameList.add(
|
||||
SuspendCoroutineStackFrameItem(
|
||||
firstSuspendedStackFrameProxyImpl,
|
||||
suspendedFrame,
|
||||
coroutine.lastObservedFrameFieldRef,
|
||||
location
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (preflightFound) {
|
||||
val coroutineStack = coroutineStackFrameProvider.lookupForResumeContinuation(runningStackFrameProxy, suspendContext)
|
||||
if (coroutineStack?.isNotEmpty() == true) {
|
||||
// clue coroutine stack into the thread's real stack
|
||||
|
||||
coroutine.stackTrace.subList(creationFrameSeparatorIndex + 1, coroutine.stackTrace.size).forEach {
|
||||
val location = createLocation(it)
|
||||
coroutineStackFrameList.add(CreationCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, it, location))
|
||||
}
|
||||
for (asyncFrame in coroutineStack) {
|
||||
coroutineStackFrameList.add(
|
||||
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)
|
||||
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) =
|
||||
frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" &&
|
||||
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 org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||
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.proxy.CoroutineDebugProbesProxy
|
||||
|
||||
@@ -43,14 +42,14 @@ class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
|
||||
val states = CoroutineDebugProbesProxy(context.suspendContext ?: return)
|
||||
.dumpCoroutines()
|
||||
if (states.isOk()) {
|
||||
val message = KotlinDebuggerCoroutinesBundle.message("coroutine.dump.failed")
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message,MessageType.ERROR).notify(project)
|
||||
} else {
|
||||
val f = fun() {
|
||||
val ui = session.xDebugSession?.ui ?: return
|
||||
addCoroutineDump(project, states.cache, ui, session.searchScope)
|
||||
}
|
||||
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
|
||||
}
|
||||
val debuggerSession = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession
|
||||
presentation.isEnabled = debuggerSession != null && debuggerSession.isAttached && coroutineDebuggerEnabled()
|
||||
presentation.isEnabled = debuggerSession != null && debuggerSession.isAttached
|
||||
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")
|
||||
}
|
||||
+87
-10
@@ -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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.data
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||
import com.intellij.ui.ColoredTextContainer
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.xdebugger.frame.XCompositeNode
|
||||
import com.intellij.xdebugger.frame.XNamedValue
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ObjectReference
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
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
|
||||
|
||||
class CreationCoroutineStackFrameItem(
|
||||
val frame: StackFrameProxyImpl,
|
||||
val stackTraceElement: StackTraceElement,
|
||||
location: Location
|
||||
) : CoroutineStackFrameItem(location, emptyList()) {
|
||||
fun emptyDescriptor() =
|
||||
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspended frames in Suspended coroutine
|
||||
*/
|
||||
class SuspendCoroutineStackFrameItem(
|
||||
val frame: StackFrameProxyImpl,
|
||||
val stackTraceElement: StackTraceElement,
|
||||
val lastObservedFrameFieldRef: ObjectReference,
|
||||
location: Location,
|
||||
spilledVariables: List<XNamedValue> = emptyList()
|
||||
) : CoroutineStackFrameItem(location, spilledVariables) {
|
||||
fun emptyDescriptor() =
|
||||
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||
}
|
||||
|
||||
class RunningCoroutineStackFrameItem(
|
||||
val frame: StackFrameProxyImpl,
|
||||
val stackFrame: XStackFrame,
|
||||
// val stackFrame: XStackFrame,
|
||||
spilledVariables: List<XNamedValue> = emptyList()
|
||||
) : CoroutineStackFrameItem(frame.location(), spilledVariables)
|
||||
|
||||
/**
|
||||
* Restored frame in Running coroutine, attaching to running thread
|
||||
*/
|
||||
class RestoredCoroutineStackFrameItem(
|
||||
val frame: StackFrameProxyImpl,
|
||||
location: Location,
|
||||
@@ -52,8 +59,71 @@ class RestoredCoroutineStackFrameItem(
|
||||
StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||
}
|
||||
|
||||
/**
|
||||
* Restored from memory dump
|
||||
*/
|
||||
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>) :
|
||||
StackFrameItem(location, spilledVariables) {
|
||||
@@ -63,4 +133,11 @@ sealed class CoroutineStackFrameItem(val location: Location, val spilledVariable
|
||||
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
|
||||
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
|
||||
}
|
||||
|
||||
fun fail() {
|
||||
fun fail(): CoroutineInfoCache {
|
||||
cache.clear()
|
||||
state = CacheState.FAIL
|
||||
return this
|
||||
}
|
||||
|
||||
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.ThreadReference
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineHolder
|
||||
|
||||
/**
|
||||
* Represents state of a coroutine.
|
||||
* @see `kotlinx.coroutines.debug.CoroutineInfo`
|
||||
*/
|
||||
data class CoroutineInfoData(
|
||||
val name: String,
|
||||
val state: State,
|
||||
|
||||
val stackTrace: List<StackTraceElement>,
|
||||
// links to jdi.* references
|
||||
val key: CoroutineNameIdState,
|
||||
val stackTrace: List<CoroutineStackFrameItem>,
|
||||
val creationStackTrace: List<CreationCoroutineStackFrameItem>,
|
||||
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
||||
val lastObservedFrameFieldRef: ObjectReference?
|
||||
) {
|
||||
@@ -26,22 +25,41 @@ data class CoroutineInfoData(
|
||||
// @TODO for refactoring/removal along with DumpPanel
|
||||
val stringStackTrace: String by lazy {
|
||||
buildString {
|
||||
appendln("\"$name\", state: $state")
|
||||
appendln("\"${key.name}\", state: ${key.state}")
|
||||
stackTrace.forEach {
|
||||
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 {
|
||||
RUNNING,
|
||||
SUSPENDED,
|
||||
CREATED
|
||||
fun isRunning() = key.state == State.RUNNING
|
||||
|
||||
companion object {
|
||||
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() {
|
||||
lateinit var icon: Icon
|
||||
|
||||
override fun getName() = infoData.name
|
||||
override fun getName() = infoData.key.name
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
|
||||
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 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 {
|
||||
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
|
||||
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
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.data.CoroutineInfoCache
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
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 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
|
||||
* Should be invoked on debugger manager thread
|
||||
*/
|
||||
@Synchronized
|
||||
fun dumpCoroutines() : CoroutineInfoCache {
|
||||
fun dumpCoroutines(): CoroutineInfoCache {
|
||||
DebuggerManagerThreadImpl.assertIsManagerThread()
|
||||
val coroutineInfoCache = CoroutineInfoCache()
|
||||
try {
|
||||
val infoList = dump()
|
||||
val executionContext = suspendContext.executionContext() ?: return coroutineInfoCache.fail()
|
||||
val libraryAgentProxy = findProvider(executionContext)
|
||||
val infoList = libraryAgentProxy.dumpCoroutinesInfo()
|
||||
coroutineInfoCache.ok(infoList)
|
||||
} catch (e: Throwable) {
|
||||
log.error("Exception is thrown by calling dumpCoroutines.", e)
|
||||
@@ -54,189 +35,8 @@ class CoroutineDebugProbesProxy(val suspendContext: XSuspendContext) {
|
||||
return coroutineInfoCache
|
||||
}
|
||||
|
||||
private fun dump(): List<CoroutineInfoData> {
|
||||
val coroutinesInfo = dumpCoroutinesInfo() ?: return emptyList()
|
||||
|
||||
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)
|
||||
private fun findProvider(executionContext: DefaultExecutionContext) =
|
||||
CoroutineLibraryAgentProxy.instance(executionContext) ?: CoroutineNoLibraryProxy(executionContext)
|
||||
|
||||
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
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.JavaDebugProcess
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.intellij.debugger.impl.PrioritizedTask
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.xdebugger.XDebugProcess
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import java.awt.Component
|
||||
|
||||
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) =
|
||||
ManagerThreadExecutorInstance(suspendContext as SuspendContextImpl, priority)
|
||||
ManagerThreadExecutorInstance(suspendContext, priority)
|
||||
|
||||
inner class ManagerThreadExecutorInstance(
|
||||
val suspendContext: SuspendContextImpl,
|
||||
val priority: PrioritizedTask.Priority = PrioritizedTask.Priority.NORMAL
|
||||
) {
|
||||
|
||||
constructor(sc: XSuspendContext, priority: PrioritizedTask.Priority) : this(sc as SuspendContextImpl, priority)
|
||||
|
||||
fun schedule(f: (SuspendContextImpl) -> Unit) {
|
||||
val suspendContextCommand = object : SuspendContextCommandImpl(suspendContext) {
|
||||
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
|
||||
|
||||
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.ui.MessageType
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
@@ -34,9 +31,6 @@ fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSource
|
||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
||||
}
|
||||
|
||||
class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
||||
StackFrameDescriptorImpl(proxy, MethodsTracker())
|
||||
|
||||
class ProjectNotification(val project: Project) {
|
||||
fun error(message: String) =
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project)
|
||||
|
||||
+1
-1
@@ -37,4 +37,4 @@ class LoggerDelegate : ReadOnlyProperty<Any, Logger> {
|
||||
logger = Logger.getInstance(thisRef.javaClass)
|
||||
return logger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-9
@@ -34,6 +34,7 @@ import com.intellij.util.PlatformIcons
|
||||
import com.intellij.util.ui.EmptyIcon
|
||||
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.State
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Color
|
||||
import java.awt.datatransfer.StringSelection
|
||||
@@ -143,7 +144,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
var index = 0
|
||||
val states = if (UISettings.instance.state.mergeEqualStackTraces) mergedDump else dump
|
||||
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)
|
||||
if (selection === state) {
|
||||
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
|
||||
|
||||
private fun getCoroutineStateIcon(infoData: CoroutineInfoData): Icon {
|
||||
return when (infoData.state) {
|
||||
CoroutineInfoData.State.RUNNING -> LayeredIcon(AllIcons.Actions.Resume, Daemon_sign)
|
||||
CoroutineInfoData.State.SUSPENDED -> AllIcons.Actions.Pause
|
||||
return when (infoData.key.state) {
|
||||
State.RUNNING -> LayeredIcon(AllIcons.Actions.Resume, Daemon_sign)
|
||||
State.SUSPENDED -> AllIcons.Actions.Pause
|
||||
else -> EmptyIcon.create(6)
|
||||
}
|
||||
}
|
||||
@@ -190,18 +191,18 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
private fun getAttributes(infoData: CoroutineInfoData): SimpleTextAttributes {
|
||||
return when {
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private inner class CoroutineListCellRenderer : ColoredListCellRenderer<Any>() {
|
||||
|
||||
override fun customizeCellRenderer(list: JList<*>, value: Any, index: Int, selected: Boolean, hasFocus: Boolean) {
|
||||
val state = value as CoroutineInfoData
|
||||
icon = getCoroutineStateIcon(state)
|
||||
val attrs = getAttributes(state)
|
||||
val infoData = value as CoroutineInfoData
|
||||
val state = infoData.key
|
||||
icon = fromState(state.state)
|
||||
val attrs = getAttributes(infoData)
|
||||
append(state.name + " (", attrs)
|
||||
var detail: String? = state.state.name
|
||||
if (detail == null) {
|
||||
|
||||
-1
@@ -11,7 +11,6 @@ import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.XDebugSessionListener
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
|
||||
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.settings.ThreadsViewSettings
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.highlighter.JavaHighlightingColors
|
||||
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
|
||||
import com.intellij.icons.AllIcons.General.Information
|
||||
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.markup.TextAttributes
|
||||
import com.intellij.ui.ColoredTextContainer
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.xdebugger.frame.presentation.XValuePresentation
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
|
||||
import com.intellij.xdebugger.ui.DebuggerColors
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
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.State
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import java.awt.Color
|
||||
import javax.swing.Icon
|
||||
|
||||
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 {
|
||||
val log by logger
|
||||
private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance()
|
||||
|
||||
fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon {
|
||||
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 icon = when (infoData.state) {
|
||||
CoroutineInfoData.State.SUSPENDED -> AllIcons.Debugger.ThreadSuspended
|
||||
CoroutineInfoData.State.RUNNING -> AllIcons.Debugger.ThreadRunning
|
||||
CoroutineInfoData.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
|
||||
}
|
||||
val icon = fromState(infoData.key.state)
|
||||
|
||||
val label = SimpleColoredTextIcon(icon, true)
|
||||
val hasChildren = infoData.stackTrace.isNotEmpty() || infoData.creationStackTrace.isNotEmpty()
|
||||
val label = SimpleColoredTextIcon(icon, hasChildren)
|
||||
label.append("\"")
|
||||
label.appendValue(infoData.name)
|
||||
label.append("\": ${infoData.state}")
|
||||
if(name.isNotEmpty()) {
|
||||
label.appendValue(infoData.key.name)
|
||||
label.append("\": ${infoData.key.state}")
|
||||
if (name.isNotEmpty()) {
|
||||
label.append(" on thread \"")
|
||||
label.appendValue(name)
|
||||
label.append("\": $threadState")
|
||||
@@ -161,7 +161,7 @@ class SimpleColoredTextIconPresentationRenderer {
|
||||
} else {
|
||||
label.append(name.substring(dotIndex + 1))
|
||||
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) =
|
||||
SimpleColoredTextIcon(
|
||||
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) =
|
||||
SimpleColoredTextIcon(AllIcons.Actions.Lightning,false, error)
|
||||
SimpleColoredTextIcon(AllIcons.Actions.Lightning,false, KotlinDebuggerCoroutinesBundle.message(error))
|
||||
|
||||
fun renderRoorNode(text: String) =
|
||||
SimpleColoredTextIcon(null, true, text)
|
||||
fun renderInfoNode(text: String) =
|
||||
SimpleColoredTextIcon(AllIcons.General.Information, false, KotlinDebuggerCoroutinesBundle.message(text))
|
||||
|
||||
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
|
||||
|
||||
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.ide.CommonActionsManager
|
||||
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.project.Project
|
||||
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.ComboboxSpeedSearch
|
||||
import com.intellij.ui.DoubleClickListener
|
||||
import com.intellij.ui.border.CustomLineBorder
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import com.intellij.util.SingleAlarm
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.frame.*
|
||||
import com.intellij.xdebugger.impl.actions.XDebuggerActions
|
||||
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.XDebuggerTreeRestorer
|
||||
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.XValueNodeImpl
|
||||
import com.sun.jdi.request.EventRequest
|
||||
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.KotlinDebuggerCoroutinesBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
|
||||
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.LookupContinuation
|
||||
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.CreateContentParamsProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.XDebugSessionListenerProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.JPanel
|
||||
|
||||
|
||||
@@ -70,21 +57,20 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
val someCombobox = ComboBox<String>()
|
||||
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
|
||||
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
|
||||
val javaDebugProcess = session.debugProcess as JavaDebugProcess
|
||||
val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
|
||||
// val javaDebugProcess =
|
||||
// val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
|
||||
val renderer = SimpleColoredTextIconPresentationRenderer()
|
||||
val managerThreadExecutor = ManagerThreadExecutor(debugProcess)
|
||||
val applicationThreadExecutor = ApplicationThreadExecutor()
|
||||
val managerThreadExecutor = ManagerThreadExecutor(session)
|
||||
var treeState: XDebuggerTreeState? = null
|
||||
private var restorer: XDebuggerTreeRestorer? = null
|
||||
private var selectedNodeListener = XDebuggerTreeSelectedNodeListener(panel.tree)
|
||||
private var selectedNodeListener: XDebuggerTreeSelectedNodeListener? = null
|
||||
|
||||
companion object {
|
||||
private val VIEW_CLEAR_DELAY = 100 //ms
|
||||
}
|
||||
|
||||
init {
|
||||
someCombobox.setRenderer(versionedImplementationProvider.comboboxListCellRenderer())
|
||||
someCombobox.renderer = versionedImplementationProvider.comboboxListCellRenderer()
|
||||
object : ComboboxSpeedSearch(someCombobox) {
|
||||
override fun getElementText(element: Any?): String? {
|
||||
return element.toString()
|
||||
@@ -93,14 +79,14 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
someCombobox.addItem(null)
|
||||
val myToolbar = createToolbar()
|
||||
val myThreadsPanel = Wrapper()
|
||||
myThreadsPanel.setBorder(CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0))
|
||||
myThreadsPanel.add(myToolbar?.getComponent(), BorderLayout.EAST)
|
||||
myThreadsPanel.border = CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0)
|
||||
myThreadsPanel.add(myToolbar?.component, BorderLayout.EAST)
|
||||
myThreadsPanel.add(someCombobox, BorderLayout.CENTER)
|
||||
mainPanel.add(panel.mainPanel, BorderLayout.CENTER)
|
||||
selectedNodeListener.installOn()
|
||||
selectedNodeListener = XDebuggerTreeSelectedNodeListener(session, panel.tree)
|
||||
selectedNodeListener?.installOn()
|
||||
}
|
||||
|
||||
|
||||
private fun createToolbar(): ActionToolbarImpl? {
|
||||
val framesGroup = DefaultActionGroup()
|
||||
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)
|
||||
if (treeState != null) {
|
||||
restorer?.dispose()
|
||||
@@ -136,7 +122,10 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
restorer?.dispose()
|
||||
if (restorer != null) {
|
||||
restorer?.dispose()
|
||||
restorer = null
|
||||
}
|
||||
}
|
||||
|
||||
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 XCoroutinesRootNode(suspendContext: XSuspendContext) :
|
||||
inner class XCoroutinesRootNode(suspendContext: SuspendContextImpl) :
|
||||
XValueContainerNode<CoroutineGroupContainer>(
|
||||
panel.tree, null, false,
|
||||
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) {
|
||||
if (suspendContext is SuspendContextImpl && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
|
||||
node.addChildren(groups, true)
|
||||
} else {
|
||||
@@ -178,7 +167,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
}
|
||||
|
||||
inner class CoroutineContainer(
|
||||
val suspendContext: XSuspendContext,
|
||||
val suspendContext: SuspendContextImpl,
|
||||
val groupName: String
|
||||
) : RendererContainer(renderer.renderGroup(groupName)) {
|
||||
|
||||
@@ -189,38 +178,44 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
var coroutineCache = debugProbesProxy.dumpCoroutines()
|
||||
if (coroutineCache.isOk()) {
|
||||
val children = XValueChildrenList()
|
||||
coroutineCache.cache.forEach {
|
||||
children.add(FramesContainer(it, suspendContext))
|
||||
for (coroutineInfo in coroutineCache.cache) {
|
||||
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 {
|
||||
val errorNode = ErrorNode(KotlinDebuggerCoroutinesBundle.message("coroutine.view.fetching.error"))
|
||||
val errorNode = ErrorNode("coroutine.view.fetching.error")
|
||||
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 FramesContainer(
|
||||
private val infoData: CoroutineInfoData,
|
||||
private val suspendContext: XSuspendContext
|
||||
private val suspendContext: SuspendContextImpl
|
||||
) : RendererContainer(renderer.render(infoData)) {
|
||||
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
managerThreadExecutor.on(suspendContext).schedule {
|
||||
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
|
||||
val children = XValueChildrenList()
|
||||
debugProbesProxy.frameBuilder().build(infoData)
|
||||
var stackFrames = debugProbesProxy.frameBuilder().build(infoData)
|
||||
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
|
||||
infoData.stackFrameList.forEach {
|
||||
if (it is CreationCoroutineStackFrameItem)
|
||||
creationStack.add(it)
|
||||
else if (it is CoroutineStackFrameItem)
|
||||
children.add(CoroutineFrameValue(it))
|
||||
for (frame in stackFrames) {
|
||||
if (frame is CreationCoroutineStackFrameItem)
|
||||
creationStack.add(frame)
|
||||
else if (frame is CoroutineStackFrameItem)
|
||||
children.add(CoroutineFrameValue(infoData, frame))
|
||||
}
|
||||
children.add(CreationFramesContainer(infoData, creationStack))
|
||||
if (creationStack.isNotEmpty())
|
||||
children.add(CreationFramesContainer(infoData, creationStack))
|
||||
node.addChildren(children, true)
|
||||
}
|
||||
}
|
||||
@@ -235,17 +230,18 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
val children = XValueChildrenList()
|
||||
|
||||
creationFrames.forEach {
|
||||
children.add(CoroutineFrameValue(it))
|
||||
children.add(CoroutineFrameValue(infoData, it))
|
||||
}
|
||||
node.addChildren(children, true)
|
||||
}
|
||||
}
|
||||
|
||||
inner class CoroutineFrameValue(
|
||||
val frame: CoroutineStackFrameItem
|
||||
) : XNamedValue(frame.uniqueId()) {
|
||||
val infoData: CoroutineInfoData,
|
||||
val frameItem: CoroutineStackFrameItem
|
||||
) : XNamedValue(frameItem.uniqueId()) {
|
||||
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) =
|
||||
@@ -256,137 +252,4 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
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
|
||||
|
||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
@@ -37,7 +38,7 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
|
||||
val frameProxy = this.frameProxy
|
||||
if (frameProxy != null) {
|
||||
try {
|
||||
val stackTrace = asyncStackTraceProvider.getAsyncStackTraceSafe(frameProxy, this)
|
||||
val stackTrace = asyncStackTraceProvider.lookupForResumeContinuation(frameProxy, this)
|
||||
if (stackTrace != null && stackTrace.isNotEmpty()) {
|
||||
print(renderAsyncStackTrace(stackTrace), ProcessOutputTypes.SYSTEM)
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ abstract class AbstractCoroutineDumpTest : KotlinDescriptorTestCaseWithStepping(
|
||||
|
||||
private fun stringDump(infoData: List<CoroutineInfoData>) = buildString {
|
||||
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? {
|
||||
val type = instance.referenceType()
|
||||
type.allMethods()
|
||||
val method =
|
||||
if (methodSignature is String)
|
||||
type.methodsByName(name, methodSignature).single()
|
||||
|
||||
@@ -189,10 +189,22 @@
|
||||
description="Enable bytecode instrumentation for Kotlin classes"
|
||||
defaultValue="false"
|
||||
restartRequired="false"/>
|
||||
<registryKey key="kotlin.debugger.coroutines"
|
||||
description="Enable debugging for coroutines in Kotlin/JVM"
|
||||
<registryKey key="kotlin.debugger.coroutines.standalone"
|
||||
description="Enable debugging for coroutines in Kotlin/JVM with no library support"
|
||||
defaultValue="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 defaultExtensionNs="org.jetbrains.uast">
|
||||
|
||||
@@ -380,6 +380,9 @@
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.debugger.StackFrameInterceptor"
|
||||
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"/>
|
||||
|
||||
<registryKey
|
||||
|
||||
@@ -56,4 +56,8 @@ org.jetbrains.kotlin.shortenRefs.ShortenRefsTestGenerated.testExtensionForObject
|
||||
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.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