mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-04-16 15:52:18 +00:00
Compare commits
13 Commits
get-script
...
rr/vladimi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6aa155dc9 | ||
|
|
9ac2a4b0cd | ||
|
|
0bd16edc66 | ||
|
|
1bf6baefe4 | ||
|
|
59eaa80559 | ||
|
|
12a8acfcfd | ||
|
|
d87943e8a5 | ||
|
|
84711eb3a8 | ||
|
|
a801f2990c | ||
|
|
2acb5726bc | ||
|
|
35210ff83c | ||
|
|
972b3044a3 | ||
|
|
d52da5200e |
@@ -240,6 +240,14 @@ fun main(args: Array<String>) {
|
||||
// TODO: implement mapping logic for terminal operations
|
||||
model("sequence/streams/sequence", excludeDirs = listOf("terminal"))
|
||||
}
|
||||
|
||||
testClass<AbstractContinuationStackTraceTest> {
|
||||
model("continuation")
|
||||
}
|
||||
|
||||
testClass<AbstractXCoroutinesStackTraceTest> {
|
||||
model("xcoroutines")
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/tests", "idea/testData") {
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
package org.jetbrains.kotlin.idea.configuration
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
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() {
|
||||
@@ -24,16 +21,17 @@ class KotlinGradleCoroutineDebugProjectResolver : AbstractProjectResolverExtensi
|
||||
}
|
||||
|
||||
private fun setupCoroutineAgentForJvmForkedTestTasks(initScriptConsumer: Consumer<String>) {
|
||||
val lines = arrayOf(
|
||||
"gradle.taskGraph.beforeTask { Task task ->",
|
||||
" if (task instanceof Test) {",
|
||||
" def kotlinxCoroutinesDebugJar = task.classpath.find { it.name.startsWith(\"kotlinx-coroutines-debug\") }",
|
||||
" if (kotlinxCoroutinesDebugJar)",
|
||||
" task.jvmArgs (\"-javaagent:\${kotlinxCoroutinesDebugJar?.absolutePath}\", \"-ea\")",
|
||||
" }",
|
||||
"}"
|
||||
)
|
||||
val script = StringUtil.join(lines, SystemProperties.getLineSeparator())
|
||||
val script =
|
||||
//language=Gradle
|
||||
"""
|
||||
gradle.taskGraph.beforeTask { Task task ->
|
||||
if (task instanceof Test) {
|
||||
def kotlinxCoroutinesDebugJar = task.classpath.find { it.name.startsWith("kotlinx-coroutines-debug") }
|
||||
if (kotlinxCoroutinesDebugJar)
|
||||
task.jvmArgs ("-javaagent:${'$'}{kotlinxCoroutinesDebugJar?.absolutePath}", "-ea")
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
initScriptConsumer.consume(script)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ line.and.lambda.breakpoint=Line and {0,choice,1#Lambda|2#Lambdas} Breakpoints
|
||||
|
||||
filter.ignore.internal.classes=Do not step into Kotlin runtime library implementation classes
|
||||
variables.calculate.delegated.property.values=Calculate values of delegated properties (may affect program execution)
|
||||
variables.disable.coroutine.agent.values=Disable coroutine agent
|
||||
variables.disable.coroutine.agent.tooltip=Disables coroutine agent for Gradle and Java run configurations
|
||||
|
||||
field.watchpoint.tab.title=Kotlin Field Watchpoints
|
||||
field.watchpoint.properties.access=Field &access
|
||||
|
||||
@@ -22,6 +22,7 @@ class KotlinDebuggerSettings : XDebuggerSettings<KotlinDebuggerSettings>("kotlin
|
||||
var DEBUG_RENDER_DELEGATED_PROPERTIES: Boolean = false
|
||||
var DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES: Boolean = true
|
||||
var DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED: Boolean = false
|
||||
var DEBUG_DISABLE_COROUTINE_AGENT: Boolean = false
|
||||
|
||||
companion object {
|
||||
fun getInstance(): KotlinDebuggerSettings {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.KotlinDelegatedPropertyRendererConfigurableUi">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="681" height="400"/>
|
||||
@@ -17,9 +17,18 @@
|
||||
<text resource-bundle="messages/KotlinDebuggerCoreBundle" key="variables.calculate.delegated.property.values"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="5af15" class="javax.swing.JCheckBox" binding="disableCoroutineAgent">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/KotlinDebuggerCoreBundle" key="variables.disable.coroutine.agent.values"/>
|
||||
<toolTipText resource-bundle="messages/KotlinDebuggerCoreBundle" key="variables.disable.coroutine.agent.tooltip"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="c37da">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
|
||||
@@ -24,22 +24,26 @@ import javax.swing.*;
|
||||
|
||||
public class KotlinDelegatedPropertyRendererConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
|
||||
private JCheckBox renderDelegatedProperties;
|
||||
private JCheckBox disableCoroutineAgent;
|
||||
private JPanel myPanel;
|
||||
|
||||
@Override
|
||||
public void reset(@NotNull KotlinDebuggerSettings settings) {
|
||||
boolean flag = settings.getDEBUG_RENDER_DELEGATED_PROPERTIES();
|
||||
renderDelegatedProperties.setSelected(flag);
|
||||
disableCoroutineAgent.setSelected(settings.getDEBUG_DISABLE_COROUTINE_AGENT());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
|
||||
return settings.getDEBUG_RENDER_DELEGATED_PROPERTIES() != renderDelegatedProperties.isSelected();
|
||||
return settings.getDEBUG_RENDER_DELEGATED_PROPERTIES() != renderDelegatedProperties.isSelected()
|
||||
|| settings.getDEBUG_DISABLE_COROUTINE_AGENT() != disableCoroutineAgent.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull KotlinDebuggerSettings settings) {
|
||||
settings.setDEBUG_RENDER_DELEGATED_PROPERTIES(renderDelegatedProperties.isSelected());
|
||||
settings.setDEBUG_DISABLE_COROUTINE_AGENT(disableCoroutineAgent.isSelected());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -7,7 +7,9 @@ package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
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.DebuggerContextImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.sun.jdi.*
|
||||
@@ -84,6 +86,24 @@ fun <T : Any> DebugProcessImpl.invokeInManagerThread(f: (DebuggerContextImpl) ->
|
||||
return result
|
||||
}
|
||||
|
||||
fun <T : Any> SuspendContextImpl.invokeInSuspendManagerThread(debugProcessImpl: DebugProcessImpl, f: (SuspendContextImpl) -> T?): T? {
|
||||
var result: T? = null
|
||||
val command: SuspendContextCommandImpl = object : SuspendContextCommandImpl(this) {
|
||||
override fun contextAction() {
|
||||
result = runReadAction { f(this@invokeInSuspendManagerThread) }
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
DebuggerManagerThreadImpl.isManagerThread() ->
|
||||
debugProcessImpl.managerThread.invoke(command)
|
||||
else ->
|
||||
debugProcessImpl.managerThread.invokeAndWait(command)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun lambdaOrdinalByArgument(elementAt: KtFunction, context: BindingContext): Int {
|
||||
val type = asmTypeForAnonymousClass(context, elementAt)
|
||||
return type.className.substringAfterLast("$").toInt()
|
||||
|
||||
@@ -5,6 +5,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":idea:jvm-debugger:jvm-debugger-core"))
|
||||
implementation("org.apache.maven:maven-artifact:3.6.3")
|
||||
|
||||
compileOnly(toolsJarApi())
|
||||
compileOnly(intellijDep())
|
||||
|
||||
@@ -15,12 +15,11 @@ coroutine.dump.full.title=Full coroutine dump
|
||||
coroutine.dump.full.copied=Full coroutine dump was successfully copied to clipboard
|
||||
|
||||
coroutine.dump.creation.trace=Coroutine creation stack trace
|
||||
coroutine.dump.creation.frame=Creation stack frame of {0}
|
||||
|
||||
coroutine.dump.threads.loading=Loading…
|
||||
|
||||
coroutine.view.title=Coroutines
|
||||
coroutine.view.default.group=Default group
|
||||
coroutine.view.dispatcher.empty=Empty dispatcher
|
||||
coroutine.view.fetching.error=An error occurred on fetching information
|
||||
coroutine.view.fetching.not_found=No coroutine information found
|
||||
to.enable.information.breakpoint.suspend.policy.should.be.set.to.all.threads=To enable information breakpoint suspend policy should be set to 'All' threads.
|
||||
|
||||
@@ -8,20 +8,19 @@ package org.jetbrains.kotlin.idea.debugger.coroutine
|
||||
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.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.CoroutinePreflightStackFrame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackFrameItem
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.threadAndContextSupportsEvaluation
|
||||
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<StackFrameItem>? {
|
||||
val stackFrameList = hopelessAware {
|
||||
if (stackFrame is CoroutinePreflightStackFrame)
|
||||
lookupForAfterPreflight(stackFrame, suspendContext)
|
||||
processPreflight(stackFrame, suspendContext)
|
||||
else
|
||||
null
|
||||
}
|
||||
@@ -30,46 +29,27 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
|
||||
else stackFrameList
|
||||
}
|
||||
|
||||
fun lookupForAfterPreflight(
|
||||
stackFrame: CoroutinePreflightStackFrame,
|
||||
private fun processPreflight(
|
||||
preflightFrame: CoroutinePreflightStackFrame,
|
||||
suspendContext: SuspendContextImpl
|
||||
): List<CoroutineStackFrameItem>? {
|
||||
val resumeWithFrame = stackFrame.resumeWithFrame
|
||||
val resumeWithFrame = preflightFrame.threadPreCoroutineFrames.firstOrNull()
|
||||
|
||||
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
|
||||
if (threadAndContextSupportsEvaluation(
|
||||
suspendContext,
|
||||
resumeWithFrame
|
||||
)
|
||||
) {
|
||||
val doubleFrameList = CoroutineFrameBuilder.build(preflightFrame, suspendContext)
|
||||
val resultList = doubleFrameList.stackTrace + doubleFrameList.creationStackTrace
|
||||
return PreflightProvider(preflightFrame, resultList)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun lookupForResumeContinuation(
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
suspendContext: SuspendContextImpl
|
||||
): List<CoroutineStackFrameItem>? {
|
||||
val location = frameProxy.location()
|
||||
if (!location.isInKotlinSources())
|
||||
return null
|
||||
|
||||
if (threadAndContextSupportsEvaluation(suspendContext, frameProxy))
|
||||
return ContinuationHolder.lookupForResumeMethodContinuation(suspendContext, frameProxy)
|
||||
?.getAsyncStackTraceIfAny()?.stackFrameItems
|
||||
return null
|
||||
}
|
||||
|
||||
private fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl) =
|
||||
suspendContext.supportsEvaluation() && frameProxy.threadProxy().supportsEvaluation()
|
||||
}
|
||||
|
||||
class PreflightProvider(private val preflight: CoroutinePreflightStackFrame, stackFrames: List<CoroutineStackFrameItem>) :
|
||||
List<CoroutineStackFrameItem> by stackFrames {
|
||||
fun getPreflight() =
|
||||
preflight
|
||||
}
|
||||
@@ -12,14 +12,15 @@ import com.intellij.execution.configurations.RunnerSettings;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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 {
|
||||
@NotNull T configuration, @NotNull JavaParameters params, RunnerSettings runnerSettings
|
||||
) {
|
||||
if (configuration != null) {
|
||||
Project project = configuration.getProject();
|
||||
DebuggerListener listener = ServiceManager.getService(project, DebuggerListener.class);
|
||||
@@ -31,7 +32,7 @@ public class CoroutineDebugConfigurationExtension extends RunConfigurationExtens
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableFor(RunConfigurationBase<?> configuration) {
|
||||
public boolean isApplicableFor(@NotNull RunConfigurationBase<?> configuration) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,35 @@ import com.intellij.debugger.actions.AsyncStacksToggleAction
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.openapi.application.Application
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
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
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isInUnitTest
|
||||
|
||||
class CoroutineStackFrameInterceptor(val project: Project) : StackFrameInterceptor {
|
||||
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? =
|
||||
if (debugProcess.xdebugProcess?.session is XDebugSessionImpl &&
|
||||
AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)
|
||||
)
|
||||
ContinuationHolder.coroutineExitFrame(frame, debugProcess.debuggerContext.suspendContext as SuspendContextImpl)
|
||||
else
|
||||
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? {
|
||||
val stackFrame = if (debugProcess.xdebugProcess?.session is XDebugSessionImpl
|
||||
&& frame !is SkipCoroutineStackFrameProxyImpl
|
||||
&& AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)
|
||||
) {
|
||||
val suspendContextImpl = when {
|
||||
isInUnitTest() -> debugProcess.suspendManager.pausedContext
|
||||
else -> debugProcess.debuggerContext.suspendContext
|
||||
}
|
||||
if (!isInUnitTest())
|
||||
assert(debugProcess.suspendManager.pausedContext === debugProcess.debuggerContext.suspendContext)
|
||||
suspendContextImpl?.let {
|
||||
CoroutineFrameBuilder.coroutineExitFrame(frame, it)
|
||||
}
|
||||
} else
|
||||
null
|
||||
return stackFrame
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,8 @@
|
||||
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
|
||||
@@ -18,7 +16,6 @@ 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
|
||||
@@ -26,6 +23,7 @@ import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.XDebuggerManagerListener
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion
|
||||
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
|
||||
@@ -44,19 +42,39 @@ class DebuggerConnection(
|
||||
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.")
|
||||
val kotlinxCoroutinesCore = params.classPath?.pathList?.firstOrNull { it.contains("kotlinx-coroutines-core") }
|
||||
val kotlinxCoroutinesDebug = params.classPath?.pathList?.firstOrNull { it.contains("kotlinx-coroutines-debug") }
|
||||
|
||||
val mode = if (kotlinxCoroutinesDebug != null) {
|
||||
CoroutineDebuggerMode.VERSION_UP_TO_1_3_5
|
||||
} else if (kotlinxCoroutinesCore != null) {
|
||||
determineCoreVersionMode(kotlinxCoroutinesCore)
|
||||
} else
|
||||
CoroutineDebuggerMode.DISABLED
|
||||
|
||||
when (mode) {
|
||||
CoroutineDebuggerMode.VERSION_1_3_6_AND_UP -> initializeCoroutineAgent(params, kotlinxCoroutinesCore)
|
||||
CoroutineDebuggerMode.VERSION_UP_TO_1_3_5 -> initializeCoroutineAgent(params, kotlinxCoroutinesDebug)
|
||||
else -> log.debug("CoroutineDebugger disabled.")
|
||||
}
|
||||
}
|
||||
connect()
|
||||
}
|
||||
|
||||
private fun determineCoreVersionMode(kotlinxCoroutinesCore: String): CoroutineDebuggerMode {
|
||||
val regex = Regex(""".+\Wkotlinx\-coroutines\-core\-(.+)?\.jar""")
|
||||
val matchResult = regex.matchEntire(kotlinxCoroutinesCore) ?: return CoroutineDebuggerMode.DISABLED
|
||||
|
||||
val coroutinesCoreVersion = DefaultArtifactVersion(matchResult.groupValues.get(1))
|
||||
val versionToCompareTo = DefaultArtifactVersion("1.3.5")
|
||||
return if (versionToCompareTo.compareTo(coroutinesCoreVersion) < 0)
|
||||
CoroutineDebuggerMode.VERSION_1_3_6_AND_UP
|
||||
else
|
||||
CoroutineDebuggerMode.DISABLED
|
||||
}
|
||||
|
||||
private fun initializeCoroutineAgent(params: JavaParameters, it: String?) {
|
||||
params.vmParametersList?.add("-javaagent:$it")
|
||||
params.vmParametersList?.add("-ea")
|
||||
}
|
||||
|
||||
private fun connect() {
|
||||
@@ -64,17 +82,18 @@ class DebuggerConnection(
|
||||
connection?.subscribe(XDebuggerManager.TOPIC, this)
|
||||
}
|
||||
|
||||
override fun processStarted(debugProcess: XDebugProcess) =
|
||||
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 {
|
||||
ManagerThreadExecutor(debugProcess).on(debugProcess.session.suspendContext).invoke {
|
||||
Disposer.dispose(rootDisposable)
|
||||
disposable = null
|
||||
}
|
||||
@@ -100,6 +119,8 @@ class DebuggerConnection(
|
||||
}
|
||||
}
|
||||
|
||||
fun standaloneCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines.standalone")
|
||||
|
||||
fun coroutineDebuggerTraceEnabled() = Registry.`is`("kotlin.debugger.coroutines.trace")
|
||||
enum class CoroutineDebuggerMode {
|
||||
DISABLED,
|
||||
VERSION_UP_TO_1_3_5,
|
||||
VERSION_1_3_6_AND_UP
|
||||
}
|
||||
@@ -12,6 +12,7 @@ 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.KotlinDebuggerSettings
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
|
||||
interface DebuggerListener : XDebuggerManagerListener {
|
||||
@@ -32,10 +33,8 @@ class CoroutineDebuggerListener(val project: Project) : DebuggerListener {
|
||||
): 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?)
|
||||
val disableCoroutineAgent = KotlinDebuggerSettings.getInstance().DEBUG_DISABLE_COROUTINE_AGENT
|
||||
if (!disableCoroutineAgent && runnerSettings is DebuggingRunnerData && !isExternalSystemRunConfiguration && !isGradleConfiguration)
|
||||
return DebuggerConnection(project, configuration, params, runnerSettings)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,82 +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.command
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.engine.JavaExecutionStack
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.jdi.ClassesByNameProvider
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
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: SuspendContextImpl) {
|
||||
private val coroutineStackFrameProvider = CoroutineAsyncStackTraceProvider()
|
||||
val debugProcess = suspendContext.debugProcess
|
||||
private val virtualMachineProxy = debugProcess.virtualMachineProxy
|
||||
|
||||
companion object {
|
||||
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>()
|
||||
|
||||
if (coroutine.isRunning() && coroutine.activeThread is ThreadReference) {
|
||||
val threadReferenceProxyImpl = ThreadReferenceProxyImpl(debugProcess.virtualMachineProxy, coroutine.activeThread)
|
||||
|
||||
val realFrames = threadReferenceProxyImpl.forceFrames()
|
||||
var coroutineStackInserted = false
|
||||
var preflightFound = false
|
||||
for (runningStackFrameProxy in realFrames) {
|
||||
if (runningStackFrameProxy.location().isPreFlight()) {
|
||||
preflightFound = true
|
||||
continue
|
||||
}
|
||||
if (preflightFound) {
|
||||
val coroutineStack = coroutineStackFrameProvider.lookupForResumeContinuation(runningStackFrameProxy, suspendContext)
|
||||
if (coroutineStack?.isNotEmpty() == true) {
|
||||
// clue coroutine stack into the thread's real stack
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private fun isInvokeSuspendNegativeLineMethodFrame(frame: StackFrameProxyImpl) =
|
||||
frame.safeLocation()?.safeMethod()?.name() == "invokeSuspend" &&
|
||||
frame.safeLocation()?.safeMethod()?.signature() == "(Ljava/lang/Object;)Ljava/lang/Object;" &&
|
||||
frame.safeLocation()?.safeLineNumber() ?: 0 < 0
|
||||
}
|
||||
@@ -10,12 +10,13 @@ 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.ObjectReference
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||
|
||||
class ContinuationValueDescriptorImpl(
|
||||
project: Project,
|
||||
val continuation: ContinuationHolder,
|
||||
val continuation: ObjectReference,
|
||||
val fieldName: String,
|
||||
val variableName: String
|
||||
) : ValueDescriptorImpl(project) {
|
||||
@@ -23,7 +24,7 @@ class ContinuationValueDescriptorImpl(
|
||||
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
|
||||
val field = continuation.referenceType()?.fieldByName(fieldName) ?: return null
|
||||
return continuation.field(field)
|
||||
return continuation.getValue(field)
|
||||
}
|
||||
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||
|
||||
@@ -10,52 +10,54 @@ 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.coroutineDebuggerTraceEnabled
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isInvokeSuspend
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
|
||||
/**
|
||||
* Creation frame of coroutine either in RUNNING or SUSPENDED state.
|
||||
*/
|
||||
class CreationCoroutineStackFrameItem(
|
||||
val stackTraceElement: StackTraceElement,
|
||||
location: Location
|
||||
location: Location,
|
||||
val first: Boolean
|
||||
) : CoroutineStackFrameItem(location, emptyList()) {
|
||||
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||
fun descriptor(frame: StackFrameProxyImpl) =
|
||||
RestoredStackFrameDescriptor(stackTraceElement, frame)
|
||||
|
||||
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
|
||||
return if (first)
|
||||
CreationCoroutineStackFrame(debugProcess, this)
|
||||
else
|
||||
super.createFrame(debugProcess)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspended frames in Suspended coroutine
|
||||
* Restored frame in SUSPENDED coroutine, not attached to any thread.
|
||||
*/
|
||||
class SuspendCoroutineStackFrameItem(
|
||||
val stackTraceElement: StackTraceElement,
|
||||
location: Location,
|
||||
spilledVariables: List<XNamedValue> = emptyList()
|
||||
) : CoroutineStackFrameItem(location, spilledVariables) {
|
||||
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||
EmptyStackFrameDescriptor(stackTraceElement, frame)
|
||||
|
||||
fun descriptor(frame: StackFrameProxyImpl) =
|
||||
RestoredStackFrameDescriptor(stackTraceElement, frame)
|
||||
}
|
||||
|
||||
class RunningCoroutineStackFrameItem(
|
||||
val frame: StackFrameProxyImpl,
|
||||
// val stackFrame: XStackFrame,
|
||||
spilledVariables: List<XNamedValue> = emptyList()
|
||||
) : CoroutineStackFrameItem(frame.location(), spilledVariables)
|
||||
|
||||
/**
|
||||
* Restored frame in Running coroutine, attaching to running thread
|
||||
* Restored frame in RUNNING coroutine, attached to running thread. Frame references a 'preflight' or 'exit' frame.
|
||||
*/
|
||||
class RestoredCoroutineStackFrameItem(
|
||||
val frame: StackFrameProxyImpl,
|
||||
location: Location,
|
||||
spilledVariables: List<XNamedValue>
|
||||
) : CoroutineStackFrameItem(location, spilledVariables) {
|
||||
fun emptyDescriptor() =
|
||||
|
||||
fun descriptor() =
|
||||
StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||
}
|
||||
|
||||
@@ -65,7 +67,7 @@ class RestoredCoroutineStackFrameItem(
|
||||
class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<XNamedValue>) :
|
||||
CoroutineStackFrameItem(location, spilledVariables) {
|
||||
|
||||
fun emptyDescriptor(frame: StackFrameProxyImpl) =
|
||||
fun descriptor(frame: StackFrameProxyImpl) =
|
||||
StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||
}
|
||||
|
||||
@@ -78,66 +80,38 @@ class DefaultCoroutineStackFrameItem(location: Location, spilledVariables: List<
|
||||
* - invokeSuspend(KotlinStackFrame) -|
|
||||
* | replaced with CoroutinePreflightStackFrame
|
||||
* - resumeWith(KotlinStackFrame) ----|
|
||||
* - PreCoroutineStackFrameItem part of CoroutinePreflightStackFrame
|
||||
* - Kotlin/JavaStackFrame -> PreCoroutineStackFrameItem : CoroutinePreflightStackFrame.threadPreCoroutineFrames
|
||||
*
|
||||
*/
|
||||
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
|
||||
)
|
||||
|
||||
class RunningCoroutineStackFrameItem(
|
||||
val frame: StackFrameProxyImpl,
|
||||
location: Location,
|
||||
spilledVariables: List<XNamedValue> = emptyList()
|
||||
) : CoroutineStackFrameItem(location, 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
|
||||
val realStackFrame = debugProcess.invokeInManagerThread {
|
||||
debugProcess.positionManager.createStackFrame(frame, debugProcess, location)
|
||||
}
|
||||
super.computeChildren(node)
|
||||
return CoroutineStackFrame(debugProcess, this, realStackFrame)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
val log by logger
|
||||
|
||||
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame =
|
||||
CoroutineStackFrame(debugProcess, this)
|
||||
|
||||
fun uniqueId(): String {
|
||||
return location.safeSourceName() + ":" + location.safeMethod().toString() + ":" +
|
||||
location.safeLineNumber() + ":" + location.safeKotlinPreferredLineNumber()
|
||||
}
|
||||
|
||||
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
|
||||
return CoroutineStackFrame(debugProcess, this)
|
||||
}
|
||||
fun isInvokeSuspend(): Boolean =
|
||||
location.isInvokeSuspend()
|
||||
}
|
||||
|
||||
class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
||||
class RestoredStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameProxyImpl) :
|
||||
StackFrameDescriptorImpl(proxy, MethodsTracker())
|
||||
|
||||
@@ -8,10 +8,17 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
|
||||
class CoroutineInfoCache(
|
||||
val cache: MutableList<CoroutineInfoData> = mutableListOf(), var state: CacheState = CacheState.INIT
|
||||
) {
|
||||
fun ok(infoList: List<CoroutineInfoData>) {
|
||||
fun ok(infoList: List<CoroutineInfoData>): CoroutineInfoCache {
|
||||
cache.clear()
|
||||
cache.addAll(infoList)
|
||||
state = CacheState.OK
|
||||
return this
|
||||
}
|
||||
|
||||
fun ok(): CoroutineInfoCache {
|
||||
cache.clear()
|
||||
state = CacheState.OK
|
||||
return this
|
||||
}
|
||||
|
||||
fun fail(): CoroutineInfoCache {
|
||||
|
||||
@@ -7,7 +7,10 @@ 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
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData.Companion.DEFAULT_COROUTINE_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData.Companion.DEFAULT_COROUTINE_STATE
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
|
||||
/**
|
||||
* Represents state of a coroutine.
|
||||
@@ -18,20 +21,8 @@ data class CoroutineInfoData(
|
||||
val stackTrace: List<CoroutineStackFrameItem>,
|
||||
val creationStackTrace: List<CreationCoroutineStackFrameItem>,
|
||||
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
|
||||
val lastObservedFrameFieldRef: ObjectReference?
|
||||
val lastObservedFrame: ObjectReference? = null
|
||||
) {
|
||||
var stackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
||||
|
||||
// @TODO for refactoring/removal along with DumpPanel
|
||||
val stringStackTrace: String by lazy {
|
||||
buildString {
|
||||
appendln("\"${key.name}\", state: ${key.state}")
|
||||
stackTrace.forEach {
|
||||
appendln("\t$it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isSuspended() = key.state == State.SUSPENDED
|
||||
|
||||
fun isCreated() = key.state == State.CREATED
|
||||
@@ -40,17 +31,40 @@ data class CoroutineInfoData(
|
||||
|
||||
fun isRunning() = key.state == State.RUNNING
|
||||
|
||||
fun topRestoredFrame() = stackTrace.firstOrNull()
|
||||
|
||||
fun topFrameVariables() = stackTrace.firstOrNull()?.spilledVariables ?: emptyList()
|
||||
|
||||
fun restoredStackTrace(mode: SuspendExitMode): List<CoroutineStackFrameItem> =
|
||||
if (stackTrace.isNotEmpty() && stackTrace.first().isInvokeSuspend())
|
||||
stackTrace.drop(1)
|
||||
else if (mode == SuspendExitMode.SUSPEND_METHOD_PARAMETER)
|
||||
stackTrace.drop(1)
|
||||
else
|
||||
stackTrace
|
||||
|
||||
companion object {
|
||||
fun suspendedCoroutineInfoData(
|
||||
holder: CoroutineHolder,
|
||||
lastObservedFrameFieldRef: ObjectReference
|
||||
): CoroutineInfoData? {
|
||||
return CoroutineInfoData(holder.info, holder.stackFrameItems, emptyList(), null, lastObservedFrameFieldRef)
|
||||
}
|
||||
val log by logger
|
||||
const val DEFAULT_COROUTINE_NAME = "coroutine"
|
||||
const val DEFAULT_COROUTINE_STATE = "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
data class CoroutineNameIdState(val name: String, val id: String, val state: State)
|
||||
data class CoroutineNameIdState(val name: String, val id: String, val state: State, val dispatcher: String?) {
|
||||
|
||||
fun formatName() =
|
||||
"$name:$id"
|
||||
|
||||
companion object {
|
||||
fun instance(mirror: MirrorOfCoroutineInfo): CoroutineNameIdState =
|
||||
CoroutineNameIdState(
|
||||
mirror.context?.name ?: DEFAULT_COROUTINE_NAME,
|
||||
"${mirror.sequenceNumber}",
|
||||
State.valueOf(mirror.state ?: DEFAULT_COROUTINE_STATE),
|
||||
mirror.context?.dispatcher
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class State {
|
||||
RUNNING,
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.engine.JVMStackFrameInfoProvider
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
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.intellij.xdebugger.frame.XValueChildrenList
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.coroutineDebuggerTraceEnabled
|
||||
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 coroutineInfoData: CoroutineInfoData,
|
||||
val stackFrameDescriptorImpl: StackFrameDescriptorImpl,
|
||||
val threadPreCoroutineFrames: List<StackFrameProxyImpl>,
|
||||
val mode: SuspendExitMode,
|
||||
val firstFrameVariables: List<XNamedValue> = coroutineInfoData.topFrameVariables()
|
||||
) : KotlinStackFrame(stackFrameDescriptorImpl), JVMStackFrameInfoProvider {
|
||||
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
val childrenList = XValueChildrenList()
|
||||
firstFrameVariables.forEach {
|
||||
childrenList.add(it)
|
||||
}
|
||||
node.addChildren(childrenList, false)
|
||||
super.computeChildren(node)
|
||||
}
|
||||
|
||||
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||
// skip restored variables
|
||||
return super.getVisibleVariables().filter { v -> ! firstFrameVariables.any { it.name == v.name() } }
|
||||
}
|
||||
|
||||
override fun isInLibraryContent() = false
|
||||
|
||||
override fun isSynthetic() = false
|
||||
|
||||
fun restoredStackTrace() =
|
||||
coroutineInfoData.restoredStackTrace(mode)
|
||||
}
|
||||
|
||||
enum class SuspendExitMode {
|
||||
SUSPEND_LAMBDA, SUSPEND_METHOD_PARAMETER, SUSPEND_METHOD, UNKNOWN, NONE;
|
||||
|
||||
fun isCoroutineFound() =
|
||||
this == SUSPEND_LAMBDA || this == SUSPEND_METHOD_PARAMETER
|
||||
|
||||
fun isSuspendMethodParameter() =
|
||||
this == SUSPEND_METHOD_PARAMETER
|
||||
}
|
||||
|
||||
class CreationCoroutineStackFrame(debugProcess: DebugProcessImpl, item: StackFrameItem) : CoroutineStackFrame(debugProcess, item) {
|
||||
override fun getCaptionAboveOf() = KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.trace")
|
||||
|
||||
override fun hasSeparatorAbove(): Boolean =
|
||||
true
|
||||
}
|
||||
|
||||
open class CoroutineStackFrame(debugProcess: DebugProcessImpl, val item: StackFrameItem, val realStackFrame: XStackFrame? = null) :
|
||||
StackFrameItem.CapturedStackFrame(debugProcess, item) {
|
||||
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
if (realStackFrame != null)
|
||||
realStackFrame.computeChildren(node)
|
||||
else
|
||||
super.computeChildren(node)
|
||||
}
|
||||
|
||||
override fun getCaptionAboveOf() = "CoroutineExit"
|
||||
|
||||
override fun hasSeparatorAbove(): Boolean =
|
||||
false
|
||||
}
|
||||
@@ -6,234 +6,129 @@
|
||||
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.coroutine.data.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.*
|
||||
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
|
||||
class ContinuationHolder private constructor(val context: DefaultExecutionContext) {
|
||||
private val debugMetadata: DebugMetadata? = DebugMetadata.instance(context)
|
||||
private val locationCache = LocationCache(context)
|
||||
private val debugProbesImpl = DebugProbesImpl.instance(context)
|
||||
private val log by logger
|
||||
|
||||
fun getAsyncStackTraceIfAny(): CoroutineHolder? {
|
||||
fun extractCoroutineInfoData(continuation: ObjectReference): CoroutineInfoData? {
|
||||
try {
|
||||
return collectFrames()
|
||||
val consumer = mutableListOf<CoroutineStackFrameItem>()
|
||||
val continuationStack = debugMetadata?.fetchContinuationStack(continuation, context) ?: return null
|
||||
for (frame in continuationStack.coroutineStack) {
|
||||
val coroutineStackFrame = createStackFrameItem(frame)
|
||||
if (coroutineStackFrame != null)
|
||||
consumer.add(coroutineStackFrame)
|
||||
}
|
||||
val lastRestoredFrame = continuationStack.coroutineStack.last()
|
||||
return findCoroutineInformation(lastRestoredFrame.baseContinuationImpl.coroutineOwner, consumer)
|
||||
} catch (e: Exception) {
|
||||
log.error("Error while looking for variables.", e)
|
||||
log.error("Error while looking for stack frame.", 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)
|
||||
private fun findCoroutineInformation(
|
||||
input: ObjectReference?,
|
||||
stackFrameItems: List<CoroutineStackFrameItem>
|
||||
): CoroutineInfoData? {
|
||||
val creationStackTrace = mutableListOf<CreationCoroutineStackFrameItem>()
|
||||
val realState = if (input?.type()?.isAbstractCoroutine() == true) {
|
||||
state(input) ?: return null
|
||||
} else {
|
||||
val ci = debugProbesImpl?.getCoroutineInfo(input, context)
|
||||
if (ci != null) {
|
||||
if (ci.creationStackTrace != null)
|
||||
for (index in 0 until ci.creationStackTrace.size) {
|
||||
val frame = ci.creationStackTrace.get(index)
|
||||
val ste = frame.stackTraceElement()
|
||||
val location = locationCache.createLocation(ste)
|
||||
creationStackTrace.add(CreationCoroutineStackFrameItem(ste, location, index == 0))
|
||||
}
|
||||
CoroutineNameIdState.instance(ci)
|
||||
} else {
|
||||
CoroutineInfoData.log.warn("Coroutine agent information not found.")
|
||||
CoroutineNameIdState(CoroutineInfoData.DEFAULT_COROUTINE_NAME, "-1", State.UNKNOWN, null)
|
||||
}
|
||||
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)
|
||||
}
|
||||
return CoroutineInfoData(realState, stackFrameItems, creationStackTrace)
|
||||
}
|
||||
|
||||
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()
|
||||
fun state(value: ObjectReference?): CoroutineNameIdState? {
|
||||
value ?: return null
|
||||
val reference = JavaLangMirror(context)
|
||||
val standaloneCoroutine = StandaloneCoroutine(context)
|
||||
val standAloneCoroutineMirror = standaloneCoroutine.mirror(value, context)
|
||||
if (standAloneCoroutineMirror?.context is MirrorOfCoroutineContext) {
|
||||
val id = standAloneCoroutineMirror.context.id
|
||||
val name = standAloneCoroutineMirror.context.name ?: CoroutineInfoData.DEFAULT_COROUTINE_NAME
|
||||
val toString = reference.string(value, context)
|
||||
// trying to get coroutine information by calling JobSupport.toString(), ${nameString()}{${stateString(state)}}@$hexAddress
|
||||
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(name, id?.toString() ?: hexAddress, state, standAloneCoroutineMirror.context.dispatcher)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createStackFrameItem(
|
||||
frame: MirrorOfStackFrame
|
||||
): DefaultCoroutineStackFrameItem? {
|
||||
val stackTraceElement = frame.baseContinuationImpl.stackTraceElement?.stackTraceElement() ?: return null
|
||||
val locationClass = context.findClassSafe(stackTraceElement.className) ?: return null
|
||||
val generatedLocation = locationCache.createLocation(locationClass, stackTraceElement.methodName, stackTraceElement.lineNumber)
|
||||
val spilledVariables = frame.baseContinuationImpl.spilledValues(context)
|
||||
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 instance(context: DefaultExecutionContext) =
|
||||
ContinuationHolder(context)
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
fun MirrorOfBaseContinuationImpl.spilledValues(context: DefaultExecutionContext): List<JavaValue> {
|
||||
return fieldVariables.mapNotNull {
|
||||
it.toJavaValue(that, context)
|
||||
}
|
||||
}
|
||||
|
||||
fun FieldVariable.toJavaValue(continuation: ObjectReference, context: DefaultExecutionContext): JavaValue {
|
||||
val valueDescriptor = ContinuationValueDescriptorImpl(
|
||||
context.project,
|
||||
continuation,
|
||||
fieldName,
|
||||
variableName
|
||||
)
|
||||
return JavaValue.create(
|
||||
null,
|
||||
valueDescriptor,
|
||||
context.evaluationContext,
|
||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineBuilder
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder
|
||||
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.executionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
@@ -25,7 +26,7 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
val coroutineInfoCache = CoroutineInfoCache()
|
||||
try {
|
||||
val executionContext = suspendContext.executionContext() ?: return coroutineInfoCache.fail()
|
||||
val libraryAgentProxy = findProvider(executionContext)
|
||||
val libraryAgentProxy = findProvider(executionContext) ?: return coroutineInfoCache.ok()
|
||||
val infoList = libraryAgentProxy.dumpCoroutinesInfo()
|
||||
coroutineInfoCache.ok(infoList)
|
||||
} catch (e: Throwable) {
|
||||
@@ -35,8 +36,14 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
return coroutineInfoCache
|
||||
}
|
||||
|
||||
private fun findProvider(executionContext: DefaultExecutionContext) =
|
||||
CoroutineLibraryAgentProxy.instance(executionContext) ?: CoroutineNoLibraryProxy(executionContext)
|
||||
private fun findProvider(executionContext: DefaultExecutionContext): CoroutineInfoProvider? {
|
||||
val agentProxy = CoroutineLibraryAgent2Proxy.instance(executionContext)
|
||||
if (agentProxy != null)
|
||||
return agentProxy
|
||||
if (standaloneCoroutineDebuggerEnabled())
|
||||
return CoroutineNoLibraryProxy(executionContext)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun frameBuilder() = CoroutineBuilder(suspendContext)
|
||||
}
|
||||
fun standaloneCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines.standalone")
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.xdebugger.frame.XNamedValue
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugMetadata
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.MirrorOfCoroutineInfo
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isCreationSeparatorFrame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecutionContext) :
|
||||
CoroutineInfoProvider {
|
||||
val log by logger
|
||||
private val debugProbesImpl = DebugProbesImpl.instance(executionContext)
|
||||
private val locationCache = LocationCache(executionContext)
|
||||
private val debugMetadata: DebugMetadata? = DebugMetadata.instance(executionContext)
|
||||
|
||||
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
||||
val result = debugProbesImpl?.dumpCoroutinesInfo(executionContext) ?: emptyList()
|
||||
return result.mapNotNull { mapToCoroutineInfoData(it) }
|
||||
}
|
||||
|
||||
fun mapToCoroutineInfoData(mirror: MirrorOfCoroutineInfo): CoroutineInfoData? {
|
||||
val cnis = CoroutineNameIdState.instance(mirror)
|
||||
val stackTrace = mirror.enchancedStackTrace?.mapNotNull { it.stackTraceElement() } ?: emptyList()
|
||||
val variables: List<XNamedValue> = mirror.lastObservedFrame?.let {
|
||||
val spilledVariables = debugMetadata?.baseContinuationImpl?.mirror(it, executionContext)
|
||||
spilledVariables?.spilledValues(executionContext)
|
||||
} ?: emptyList()
|
||||
var stackFrames = findStackFrames(stackTrace, variables)
|
||||
return CoroutineInfoData(
|
||||
cnis,
|
||||
stackFrames.restoredStackFrames,
|
||||
stackFrames.creationStackFrames,
|
||||
mirror.lastObservedThread,
|
||||
mirror.lastObservedFrame
|
||||
)
|
||||
}
|
||||
|
||||
fun isInstalled(): Boolean {
|
||||
try {
|
||||
return debugProbesImpl?.isInstalledValue ?: false
|
||||
} catch (e: Exception) {
|
||||
log.error("Exception happened while checking agent status.", e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun findStackFrames(
|
||||
frames: List<StackTraceElement>,
|
||||
variables: List<XNamedValue>
|
||||
): CoroutineStackFrames {
|
||||
val index = frames.indexOfFirst { it.isCreationSeparatorFrame() }
|
||||
val restoredStackFrames = frames.take(index).map {
|
||||
SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it), variables)
|
||||
}
|
||||
val creationStackFrames = frames.subList(index + 1, frames.size).mapIndexed { ix, it ->
|
||||
CreationCoroutineStackFrameItem(it, locationCache.createLocation(it), ix == 0)
|
||||
}
|
||||
return CoroutineStackFrames(restoredStackFrames, creationStackFrames)
|
||||
}
|
||||
|
||||
data class CoroutineStackFrames(
|
||||
val restoredStackFrames: List<SuspendCoroutineStackFrameItem>,
|
||||
val creationStackFrames: List<CreationCoroutineStackFrameItem>
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun instance(executionContext: DefaultExecutionContext): CoroutineLibraryAgent2Proxy? {
|
||||
val agentProxy = CoroutineLibraryAgent2Proxy(executionContext)
|
||||
if (agentProxy.isInstalled())
|
||||
return agentProxy
|
||||
else
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,15 +5,12 @@
|
||||
|
||||
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.coroutine.util.isCreationSeparatorFrame
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, private val executionContext: DefaultExecutionContext) :
|
||||
@@ -41,8 +38,7 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva
|
||||
|
||||
// value
|
||||
private val vm = executionContext.vm
|
||||
private val classesByName = ClassesByNameProvider.createCache(vm.allClasses())
|
||||
|
||||
private val locationCache = LocationCache(executionContext)
|
||||
|
||||
private val coroutineContext: CoroutineContext = CoroutineContext(executionContext)
|
||||
|
||||
@@ -77,49 +73,23 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva
|
||||
val coroutineStackTrace = stackTrace.take(creationFrameSeparatorIndex)
|
||||
|
||||
val coroutineStackTraceFrameItems = coroutineStackTrace.map {
|
||||
SuspendCoroutineStackFrameItem(it, createLocation(it))
|
||||
SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it))
|
||||
}
|
||||
val creationStackTrace = stackTrace.subList(creationFrameSeparatorIndex + 1, stackTrace.size)
|
||||
val creationStackTraceFrameItems = creationStackTrace.map {
|
||||
CreationCoroutineStackFrameItem(it, createLocation(it))
|
||||
val creationStackTraceFrameItems = creationStackTrace.mapIndexed { index, stackTraceElement ->
|
||||
CreationCoroutineStackFrameItem(stackTraceElement, locationCache.createLocation(stackTraceElement), index == 0)
|
||||
}
|
||||
val key = CoroutineNameIdState(name,"", State.valueOf(state))
|
||||
val key = CoroutineNameIdState(name, "", State.valueOf(state), "")
|
||||
|
||||
return CoroutineInfoData(
|
||||
key,
|
||||
coroutineStackTraceFrameItems,
|
||||
coroutineStackTraceFrameItems.toMutableList(),
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -9,12 +9,16 @@ 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.findCancellableContinuationImplReferenceType
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.findCoroutineMetadataType
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.findDispatchedContinuationReferenceType
|
||||
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()
|
||||
val holder = ContinuationHolder.instance(executionContext)
|
||||
|
||||
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
|
||||
val vm = executionContext.vm
|
||||
@@ -25,7 +29,6 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
|
||||
"CANCELLABLE_CONTINUATION" -> cancellableContinuation(resultList)
|
||||
else -> dispatchedContinuation(resultList)
|
||||
}
|
||||
|
||||
} else
|
||||
log.warn("Remote JVM doesn't support canGetInstanceInfo capability (perhaps JDK-8197943).")
|
||||
return resultList
|
||||
@@ -52,9 +55,7 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
|
||||
): 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)
|
||||
return holder?.extractCoroutineInfoData(continuation)
|
||||
}
|
||||
|
||||
private fun dispatchedContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
|
||||
@@ -71,14 +72,11 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
|
||||
return false
|
||||
}
|
||||
|
||||
fun extractDispatchedContinuation(dispatchedContinuation: ObjectReference, continuation: Field): CoroutineInfoData? {
|
||||
private 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)
|
||||
return holder?.extractCoroutineInfoData(initialContinuation)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun maxCoroutines() = Registry.intValue("kotlin.debugger.coroutines.max", 1000).toLong()
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.proxy
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.jdi.ClassesByNameProvider
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
class LocationCache(val context: DefaultExecutionContext) {
|
||||
private val classesByName = ClassesByNameProvider.createCache(context.vm.allClasses())
|
||||
|
||||
fun createLocation(stackTraceElement: StackTraceElement): Location = createLocation(
|
||||
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
|
||||
stackTraceElement.methodName,
|
||||
stackTraceElement.lineNumber
|
||||
)
|
||||
|
||||
fun createLocation(
|
||||
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(context.debugProcess, type, methodName, line)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -6,11 +6,16 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.StackFrame
|
||||
|
||||
class LocationStackFrameProxyImpl(val location: Location, frame: StackFrameProxyImpl) :
|
||||
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom) {
|
||||
override fun location(): Location {
|
||||
return location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SkipCoroutineStackFrameProxyImpl(frame: StackFrameProxyImpl) :
|
||||
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom)
|
||||
|
||||
@@ -34,16 +34,18 @@ class ManagerThreadExecutor(val debugProcess: DebugProcessImpl) {
|
||||
|
||||
constructor(sc: XSuspendContext, priority: PrioritizedTask.Priority) : this(sc as SuspendContextImpl, priority)
|
||||
|
||||
fun schedule(f: (SuspendContextImpl) -> Unit) {
|
||||
val suspendContextCommand = object : SuspendContextCommandImpl(suspendContext) {
|
||||
fun invoke(f: (SuspendContextImpl) -> Unit) {
|
||||
debugProcess.managerThread.invoke(makeCommand(f))
|
||||
}
|
||||
|
||||
private fun makeCommand(f: (SuspendContextImpl) -> Unit) =
|
||||
object : SuspendContextCommandImpl(suspendContext) {
|
||||
override fun getPriority() = this@ManagerThreadExecutorInstance.priority
|
||||
|
||||
override fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
f(suspendContext)
|
||||
}
|
||||
}
|
||||
debugProcess.managerThread.invoke(suspendContextCommand)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +55,7 @@ class ApplicationThreadExecutor {
|
||||
}
|
||||
|
||||
fun schedule(f: () -> Unit, component: Component) {
|
||||
return ApplicationManager.getApplication().invokeLater( { f() }, ModalityState.stateForComponent(component))
|
||||
return ApplicationManager.getApplication().invokeLater({ f() }, ModalityState.stateForComponent(component))
|
||||
}
|
||||
|
||||
fun schedule(f: () -> Unit) {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.util.isSubTypeOrSame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
abstract class BaseDynamicMirror<T>(val value: ObjectReference, val name: String, val context: DefaultExecutionContext) {
|
||||
val log by logger
|
||||
|
||||
val cls: ReferenceType? = value.referenceType()
|
||||
|
||||
fun makeField(fieldName: String): Field? =
|
||||
cls?.let { it.fieldByName(fieldName) }
|
||||
|
||||
fun findMethod(methodName: String): Method? =
|
||||
cls?.let { it.methodsByName(methodName).single() }
|
||||
|
||||
fun findMethod(methodName: String, signature: String): Method? =
|
||||
cls?.let { it.methodsByName(methodName, signature).single() }
|
||||
|
||||
fun isCompatible(value: ObjectReference?) =
|
||||
value?.let { it.referenceType().isSubTypeOrSame(name) } ?: false
|
||||
|
||||
fun mirror(): T? {
|
||||
if (!isCompatible(value)) {
|
||||
log.trace("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?.let { it.getValue(keyFieldRef) as? ObjectReference }
|
||||
}
|
||||
|
||||
fun staticMethodValue(instance: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value?) =
|
||||
instance?.let {
|
||||
method?.let { m ->
|
||||
context.invokeMethod(it, m, values.asList()) as? ObjectReference
|
||||
}
|
||||
}
|
||||
|
||||
fun staticMethodValue(method: Method?, context: DefaultExecutionContext, vararg values: Value?) =
|
||||
cls?.let {
|
||||
method?.let {
|
||||
if (cls is ClassType)
|
||||
context.invokeMethodSafe(cls, method, values.asList()) as? ObjectReference
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun stringValue(value: ObjectReference, field: Field?) =
|
||||
field?.let {
|
||||
(value.getValue(it) as? StringReference)?.value()
|
||||
}
|
||||
|
||||
fun byteValue(value: ObjectReference, field: Field?) =
|
||||
field?.let {
|
||||
(value.getValue(it) as? ByteValue)?.value()
|
||||
}
|
||||
|
||||
fun threadValue(value: ObjectReference, field: Field?) =
|
||||
field?.let {
|
||||
value.getValue(it) as? ThreadReference
|
||||
}
|
||||
|
||||
fun stringValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext) =
|
||||
method?.let {
|
||||
(context.invokeMethod(value, it, emptyList()) as? StringReference)?.value()
|
||||
}
|
||||
|
||||
fun objectValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
|
||||
value?.let {
|
||||
method?.let {
|
||||
context.invokeMethodAsObject(value, method, *values)
|
||||
}
|
||||
}
|
||||
|
||||
fun longValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
|
||||
method?.let { (context.invokeMethod(value, it, values.asList()) as? LongValue)?.longValue() }
|
||||
|
||||
fun intValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
|
||||
method?.let { (context.invokeMethod(value, it, values.asList()) as? IntegerValue)?.intValue() }
|
||||
|
||||
fun booleanValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value): Boolean? {
|
||||
value ?: return null
|
||||
method ?: return null
|
||||
return (context.invokeMethod(value, method, values.asList()) as? BooleanValue)?.booleanValue()
|
||||
}
|
||||
|
||||
fun objectValue(value: ObjectReference, field: Field?) =
|
||||
field?.let { value.getValue(it) as ObjectReference? }
|
||||
|
||||
fun intValue(value: ObjectReference, field: Field?) =
|
||||
field?.let { (value.getValue(it) as? IntegerValue)?.intValue() }
|
||||
|
||||
fun longValue(value: ObjectReference, field: Field?) =
|
||||
field?.let { (value.getValue(it) as? LongValue)?.longValue() }
|
||||
|
||||
fun booleanValue(value: ObjectReference?, field: Field?) =
|
||||
field?.let { (value?.getValue(field) as? BooleanValue)?.booleanValue() }
|
||||
|
||||
protected abstract fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): T?
|
||||
}
|
||||
@@ -8,41 +8,46 @@ 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
|
||||
import java.lang.StackTraceElement
|
||||
|
||||
class CoroutineContext(context: DefaultExecutionContext) :
|
||||
BaseMirror<MirrorOfCoroutineContext>("kotlin.coroutines.CoroutineContext", context) {
|
||||
BaseMirror<MirrorOfCoroutineContext>("kotlin.coroutines.CombinedContext", context) {
|
||||
val coroutineNameRef = CoroutineName(context)
|
||||
val coroutineIdRef = CoroutineId(context)
|
||||
val jobRef = Job(context)
|
||||
val getContextElement: Method = makeMethod("get")
|
||||
val dispatcherRef = CoroutineDispatcher(context)
|
||||
val getContextElement = makeMethod("get")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineContext? {
|
||||
val coroutineName = getElementValue(value, context, coroutineNameRef) ?: "coroutine"
|
||||
val coroutineName = getElementValue(value, context, coroutineNameRef)
|
||||
val coroutineId = getElementValue(value, context, coroutineIdRef)
|
||||
val job = getElementValue(value, context, jobRef)
|
||||
return MirrorOfCoroutineContext(value, coroutineName, coroutineId, job)
|
||||
val dispatcher = getElementValue(value, context, dispatcherRef)
|
||||
return MirrorOfCoroutineContext(value, coroutineName, coroutineId, dispatcher, job)
|
||||
}
|
||||
|
||||
fun <T> getElementValue(value: ObjectReference, context: DefaultExecutionContext, keyProvider: ContextKey<T>): T? {
|
||||
val elementValue = objectValue(value, getContextElement, context, keyProvider.key()) ?: return null
|
||||
val key = keyProvider.key() ?: return null
|
||||
val elementValue = objectValue(value, getContextElement, context, key) ?: return null
|
||||
return keyProvider.mirror(elementValue, context)
|
||||
}
|
||||
}
|
||||
|
||||
data class MirrorOfCoroutineContext(
|
||||
val that: ObjectReference,
|
||||
val name: String,
|
||||
val name: String?,
|
||||
val id: Long?,
|
||||
val dispatcher: String?,
|
||||
val job: ObjectReference?
|
||||
)
|
||||
|
||||
abstract class ContextKey<T>(name: String, context: DefaultExecutionContext) : BaseMirror<T>(name, context) {
|
||||
abstract fun key() : ObjectReference
|
||||
abstract fun key() : ObjectReference?
|
||||
}
|
||||
|
||||
class CoroutineName(context: DefaultExecutionContext) : ContextKey<String>("kotlinx.coroutines.CoroutineName", context) {
|
||||
val key = staticObjectValue("Key")
|
||||
val getNameRef: Method = makeMethod("getName")
|
||||
val getNameRef = makeMethod("getName")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): String? {
|
||||
return stringValue(value, getNameRef, context)
|
||||
@@ -53,7 +58,7 @@ class CoroutineName(context: DefaultExecutionContext) : ContextKey<String>("kotl
|
||||
|
||||
class CoroutineId(context: DefaultExecutionContext) : ContextKey<Long>("kotlinx.coroutines.CoroutineId", context) {
|
||||
val key = staticObjectValue("Key")
|
||||
val getIdRef: Method = makeMethod("getId")
|
||||
val getIdRef = makeMethod("getId")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): Long? {
|
||||
return longValue(value, getIdRef, context)
|
||||
@@ -62,12 +67,24 @@ class CoroutineId(context: DefaultExecutionContext) : ContextKey<Long>("kotlinx.
|
||||
override fun key() = key
|
||||
}
|
||||
|
||||
class Job(context: DefaultExecutionContext) : ContextKey<ObjectReference>("kotlinx.coroutines.Job", context) {
|
||||
val key = staticObjectValue("Key")
|
||||
class Job(context: DefaultExecutionContext) : ContextKey<ObjectReference>("kotlinx.coroutines.Job\$Key", context) {
|
||||
val key = staticObjectValue("\$\$INSTANCE")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): ObjectReference? {
|
||||
return value
|
||||
}
|
||||
|
||||
override fun key() = key
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CoroutineDispatcher(context: DefaultExecutionContext) : ContextKey<String>("kotlinx.coroutines.CoroutineDispatcher", context) {
|
||||
val key = staticObjectValue("Key")
|
||||
val jlm = JavaLangMirror(context)
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): String? {
|
||||
return jlm.string(value, context)
|
||||
}
|
||||
|
||||
override fun key() = key
|
||||
}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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.util.isSubTypeOrSame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
class DebugProbesImpl private constructor(context: DefaultExecutionContext) :
|
||||
BaseMirror<MirrorOfDebugProbesImpl>("kotlinx.coroutines.debug.internal.DebugProbesImpl", context) {
|
||||
val javaLangListMirror = JavaUtilAbstractCollection(context)
|
||||
val stackTraceElement = StackTraceElement(context)
|
||||
val coroutineInfo = CoroutineInfo.instance(this, context) ?: throw IllegalStateException("CoroutineInfo implementation not found.")
|
||||
val debugProbesCoroutineOwner = DebugProbesImpl_CoroutineOwner(coroutineInfo, context)
|
||||
val instance = staticObjectValue("INSTANCE")
|
||||
val isInstalledMethod = makeMethod("isInstalled\$kotlinx_coroutines_debug", "()Z")
|
||||
?: makeMethod("isInstalled\$kotlinx_coroutines_core", "()Z") ?: throw IllegalStateException("isInstalledMethod not found")
|
||||
val isInstalledValue = booleanValue(instance, isInstalledMethod, context)
|
||||
val enhanceStackTraceWithThreadDumpMethod = makeMethod("enhanceStackTraceWithThreadDump")
|
||||
val dumpMethod = makeMethod("dumpCoroutinesInfo", "()Ljava/util/List;")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? {
|
||||
return MirrorOfDebugProbesImpl(value, instance, isInstalledValue)
|
||||
}
|
||||
|
||||
fun enchanceStackTraceWithThreadDump(
|
||||
context: DefaultExecutionContext,
|
||||
coroutineInfo: ObjectReference,
|
||||
lastObservedStackTrace: ObjectReference
|
||||
): List<MirrorOfStackTraceElement>? {
|
||||
val listReference =
|
||||
staticMethodValue(instance, enhanceStackTraceWithThreadDumpMethod, context, coroutineInfo, lastObservedStackTrace)
|
||||
val list = javaLangListMirror.mirror(listReference, context) ?: return null
|
||||
return list.values.mapNotNull { stackTraceElement.mirror(it, context) }
|
||||
}
|
||||
|
||||
fun dumpCoroutinesInfo(context: DefaultExecutionContext): List<MirrorOfCoroutineInfo> {
|
||||
instance ?: return emptyList()
|
||||
val coroutinesInfoReference = objectValue(instance, dumpMethod, context)
|
||||
val referenceList = javaLangListMirror.mirror(coroutinesInfoReference, context) ?: return emptyList()
|
||||
return referenceList.values.mapNotNull { coroutineInfo.mirror(it, context) }
|
||||
}
|
||||
|
||||
fun getCoroutineInfo(value: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? {
|
||||
val coroutineOwner = debugProbesCoroutineOwner.mirror(value, context)
|
||||
return coroutineOwner?.coroutineInfo
|
||||
}
|
||||
|
||||
companion object {
|
||||
val log by logger
|
||||
|
||||
fun instance(context: DefaultExecutionContext) =
|
||||
try {
|
||||
DebugProbesImpl(context)
|
||||
} catch (e: IllegalStateException) {
|
||||
log.warn("Attempt to access DebugProbesImpl", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DebugProbesImpl_CoroutineOwner(val coroutineInfo: CoroutineInfo, context: DefaultExecutionContext) :
|
||||
BaseMirror<MirrorOfCoroutineOwner>(COROUTINE_OWNER_CLASS_NAME, context) {
|
||||
private val infoField = makeField("info")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineOwner? {
|
||||
val info = objectValue(value, infoField)
|
||||
return MirrorOfCoroutineOwner(value, coroutineInfo.mirror(info, context))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val COROUTINE_OWNER_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugProbesImpl\$CoroutineOwner"
|
||||
|
||||
fun instanceOf(value: ObjectReference?) =
|
||||
value?.let { it.referenceType().isSubTypeOrSame(COROUTINE_OWNER_CLASS_NAME) } ?: false
|
||||
}
|
||||
}
|
||||
|
||||
data class MirrorOfCoroutineOwner(val that: ObjectReference, val coroutineInfo: MirrorOfCoroutineInfo?)
|
||||
|
||||
data class MirrorOfDebugProbesImpl(val that: ObjectReference, val instance: ObjectReference?, val isInstalled: Boolean?)
|
||||
|
||||
class CoroutineInfo private constructor(
|
||||
val debugProbesImplMirror: DebugProbesImpl,
|
||||
context: DefaultExecutionContext,
|
||||
val className: String = AGENT_134_CLASS_NAME
|
||||
) :
|
||||
BaseMirror<MirrorOfCoroutineInfo>(className, context) {
|
||||
val javaLangMirror = JavaLangMirror(context)
|
||||
val javaLangListMirror = JavaUtilAbstractCollection(context)
|
||||
private val coroutineContextMirror = CoroutineContext(context)
|
||||
private val stackTraceElement = StackTraceElement(context)
|
||||
private val contextFieldRef = makeField("context")
|
||||
private val creationStackBottom = makeField("creationStackBottom")
|
||||
private val sequenceNumberField = makeField("sequenceNumber")
|
||||
private val creationStackTraceMethod = makeMethod("getCreationStackTrace")
|
||||
private val stateMethod = makeMethod("getState")
|
||||
private val lastObservedStackTraceMethod = makeMethod("lastObservedStackTrace")
|
||||
|
||||
private val lastObservedFrameField = makeField("lastObservedFrame")
|
||||
private val lastObservedThreadField = makeField("lastObservedThread")
|
||||
|
||||
companion object {
|
||||
val log by logger
|
||||
const val AGENT_134_CLASS_NAME = "kotlinx.coroutines.debug.CoroutineInfo"
|
||||
const val AGENT_135_AND_UP_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugCoroutineInfo"
|
||||
|
||||
fun instance(debugProbesImplMirror: DebugProbesImpl, context: DefaultExecutionContext): CoroutineInfo? {
|
||||
val classType = context.findClassSafe(AGENT_134_CLASS_NAME) ?: context.findClassSafe(AGENT_135_AND_UP_CLASS_NAME) ?: return null
|
||||
try {
|
||||
return CoroutineInfo(debugProbesImplMirror, context, classType.name())
|
||||
} catch (e: IllegalStateException) {
|
||||
log.warn("coroutine-debugger: $classType not found", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineInfo {
|
||||
val state = objectValue(value, stateMethod, context)?.let {
|
||||
stringValue(it, javaLangMirror.toString, context)
|
||||
}
|
||||
val coroutineContext = coroutineContextMirror.mirror(objectValue(value, contextFieldRef), context)
|
||||
val creationStackBottomObjectReference = objectValue(value, creationStackBottom)
|
||||
val creationStackBottom =
|
||||
creationStackBottomObjectReference?.let { CoroutineStackFrame(creationStackBottomObjectReference, context).mirror() }
|
||||
val sequenceNumber = longValue(value, sequenceNumberField)
|
||||
val creationStackTraceList = objectValue(value, creationStackTraceMethod, context)
|
||||
val creationStackTraceMirror = javaLangListMirror.mirror(creationStackTraceList, context)
|
||||
val creationStackTrace = creationStackTraceMirror?.values?.mapNotNull { stackTraceElement.mirror(it, context) }
|
||||
|
||||
val lastObservedStackTrace = objectValue(value, lastObservedStackTraceMethod, context)
|
||||
val enchancedList =
|
||||
if (lastObservedStackTrace != null)
|
||||
debugProbesImplMirror.enchanceStackTraceWithThreadDump(context, value, lastObservedStackTrace)
|
||||
else emptyList()
|
||||
val lastObservedThread = threadValue(value, lastObservedThreadField)
|
||||
val lastObservedFrame = threadValue(value, lastObservedFrameField)
|
||||
return MirrorOfCoroutineInfo(
|
||||
value,
|
||||
coroutineContext,
|
||||
creationStackBottom,
|
||||
sequenceNumber,
|
||||
enchancedList,
|
||||
creationStackTrace,
|
||||
state,
|
||||
lastObservedThread,
|
||||
lastObservedFrame
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class MirrorOfCoroutineInfo(
|
||||
val that: ObjectReference,
|
||||
val context: MirrorOfCoroutineContext?,
|
||||
val creationStackBottom: MirrorOfCoroutineStackFrame?,
|
||||
val sequenceNumber: Long?,
|
||||
val enchancedStackTrace: List<MirrorOfStackTraceElement>?,
|
||||
val creationStackTrace: List<MirrorOfStackTraceElement>?,
|
||||
val state: String?,
|
||||
val lastObservedThread: ThreadReference?,
|
||||
val lastObservedFrame: ObjectReference?
|
||||
)
|
||||
|
||||
class CoroutineStackFrame(value: ObjectReference, context: DefaultExecutionContext) :
|
||||
BaseDynamicMirror<MirrorOfCoroutineStackFrame>(value, "kotlin.coroutines.jvm.internal.CoroutineStackFrame", context) {
|
||||
private val stackTraceElementMirror = StackTraceElement(context)
|
||||
private val callerFrameMethod = findMethod("getCallerFrame")
|
||||
private val getStackTraceElementMethod = findMethod("getStackTraceElement")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineStackFrame? {
|
||||
val objectReference = objectValue(value, callerFrameMethod, context)
|
||||
val callerFrame = if (objectReference is ObjectReference)
|
||||
CoroutineStackFrame(objectReference, context).mirror() else null
|
||||
val stackTraceElementReference = objectValue(value, getStackTraceElementMethod, context)
|
||||
val stackTraceElement =
|
||||
if (stackTraceElementReference is ObjectReference) stackTraceElementMirror.mirror(stackTraceElementReference, context) else null
|
||||
return MirrorOfCoroutineStackFrame(value, callerFrame, stackTraceElement)
|
||||
}
|
||||
}
|
||||
|
||||
data class MirrorOfCoroutineStackFrame(
|
||||
val that: ObjectReference,
|
||||
val callerFrame: MirrorOfCoroutineStackFrame?,
|
||||
val stackTraceElement: MirrorOfStackTraceElement?
|
||||
)
|
||||
|
||||
class StackTraceElement(context: DefaultExecutionContext) :
|
||||
BaseMirror<MirrorOfStackTraceElement>("java.lang.StackTraceElement", context) {
|
||||
private val declaringClassObjectField = makeField("declaringClass")
|
||||
private val moduleNameField = makeField("moduleName")
|
||||
private val moduleVersionField = makeField("moduleVersion")
|
||||
private val declaringClassField = makeField("declaringClass")
|
||||
private val methodNameField = makeField("methodName")
|
||||
private val fileNameField = makeField("fileName")
|
||||
private val lineNumberField = makeField("lineNumber")
|
||||
private val formatField = makeField("format")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStackTraceElement? {
|
||||
val declaringClassObject = objectValue(value, declaringClassObjectField)
|
||||
val moduleName = stringValue(value, moduleNameField)
|
||||
val moduleVersion = stringValue(value, moduleVersionField)
|
||||
val declaringClass = stringValue(value, declaringClassField)
|
||||
val methodName = stringValue(value, methodNameField)
|
||||
val fileName = stringValue(value, fileNameField)
|
||||
val lineNumber = intValue(value, lineNumberField)
|
||||
val format = byteValue(value, formatField)
|
||||
return MirrorOfStackTraceElement(
|
||||
value,
|
||||
declaringClassObject,
|
||||
moduleName,
|
||||
moduleVersion,
|
||||
declaringClass,
|
||||
methodName,
|
||||
fileName,
|
||||
lineNumber,
|
||||
format
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class MirrorOfStackTraceElement(
|
||||
val that: ObjectReference,
|
||||
val declaringClassObject: ObjectReference?,
|
||||
val moduleName: String?,
|
||||
val moduleVersion: String?,
|
||||
val declaringClass: String?,
|
||||
val methodName: String?,
|
||||
val fileName: String?,
|
||||
val lineNumber: Int?,
|
||||
val format: Byte?
|
||||
) {
|
||||
fun isInvokeSuspend() =
|
||||
"invokeSuspend" == methodName
|
||||
|
||||
fun stackTraceElement() =
|
||||
java.lang.StackTraceElement(
|
||||
declaringClass,
|
||||
methodName,
|
||||
fileName,
|
||||
lineNumber ?: -1
|
||||
)
|
||||
}
|
||||
@@ -6,57 +6,105 @@
|
||||
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.isSubTypeOrSame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
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.")
|
||||
protected val cls = context.findClassSafe(name) ?: throw IllegalStateException("coroutine-debugger: class $name not found.")
|
||||
|
||||
fun makeField(fieldName: String): Field =
|
||||
cls.fieldByName(fieldName) // childContinuation
|
||||
fun makeField(fieldName: String): Field? =
|
||||
cls?.let { it.fieldByName(fieldName) }
|
||||
|
||||
fun makeMethod(methodName: String): Method =
|
||||
cls.methodsByName(methodName).single()
|
||||
fun makeMethod(methodName: String): Method? =
|
||||
cls?.let { it.methodsByName(methodName).singleOrNull() }
|
||||
|
||||
fun isCompatible(value: ObjectReference) =
|
||||
value.referenceType().isSubTypeOrSame(name)
|
||||
fun makeMethod(methodName: String, signature: String): Method? =
|
||||
cls?.let { it.methodsByName(methodName, signature).singleOrNull() }
|
||||
|
||||
fun mirror(value: ObjectReference, context: DefaultExecutionContext): T? {
|
||||
fun isCompatible(value: ObjectReference?) =
|
||||
value?.let { it.referenceType().isSubTypeOrSame(name) } ?: false
|
||||
|
||||
fun mirror(value: ObjectReference?, context: DefaultExecutionContext): T? {
|
||||
value ?: return null
|
||||
if (!isCompatible(value)) {
|
||||
log.warn("Value ${value.referenceType()} is not compatible with $name.")
|
||||
log.trace("Value ${value.referenceType()} is not compatible with $name.")
|
||||
return null
|
||||
} else
|
||||
return fetchMirror(value, context)
|
||||
}
|
||||
|
||||
fun staticObjectValue(fieldName: String): ObjectReference {
|
||||
fun staticObjectValue(fieldName: String): ObjectReference? {
|
||||
val keyFieldRef = makeField(fieldName)
|
||||
return cls.getValue(keyFieldRef) as ObjectReference
|
||||
return cls?.let { it.getValue(keyFieldRef) as? ObjectReference }
|
||||
}
|
||||
|
||||
fun stringValue(value: ObjectReference, field: Field) =
|
||||
(value.getValue(field) as StringReference).value()
|
||||
fun staticMethodValue(instance: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value?) =
|
||||
instance?.let {
|
||||
method?.let { m ->
|
||||
context.invokeMethod(it, m, values.asList()) as? ObjectReference
|
||||
}
|
||||
}
|
||||
|
||||
fun stringValue(value: ObjectReference, method: Method, context: DefaultExecutionContext) =
|
||||
(context.invokeMethod(value, method, emptyList()) as StringReference).value()
|
||||
fun staticMethodValue(method: Method?, context: DefaultExecutionContext, vararg values: Value?) =
|
||||
cls?.let {
|
||||
method?.let {
|
||||
context.invokeMethodSafe(cls, method, values.asList()) as? ObjectReference
|
||||
}
|
||||
}
|
||||
|
||||
fun objectValue(value: ObjectReference, method: Method, context: DefaultExecutionContext, vararg values: Value) =
|
||||
context.invokeMethodAsObject(value, method, *values)
|
||||
fun stringValue(value: ObjectReference, field: Field?) =
|
||||
field?.let {
|
||||
(value.getValue(it) as? StringReference)?.value()
|
||||
}
|
||||
|
||||
fun longValue(value: ObjectReference, method: Method, context: DefaultExecutionContext, vararg values: Value) =
|
||||
(context.invokeMethodAsObject(value, method, *values) as LongValue).longValue()
|
||||
fun byteValue(value: ObjectReference, field: Field?) =
|
||||
field?.let {
|
||||
(value.getValue(it) as? ByteValue)?.value()
|
||||
}
|
||||
|
||||
fun objectValue(value: ObjectReference, field: Field) =
|
||||
value.getValue(field) as ObjectReference
|
||||
fun threadValue(value: ObjectReference, field: Field?) =
|
||||
field?.let {
|
||||
value.getValue(it) as? ThreadReference
|
||||
}
|
||||
|
||||
fun intValue(value: ObjectReference, field: Field) =
|
||||
(value.getValue(field) as IntegerValue).intValue()
|
||||
fun stringValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext) =
|
||||
method?.let {
|
||||
(context.invokeMethod(value, it, emptyList()) as? StringReference)?.value()
|
||||
}
|
||||
|
||||
fun longValue(value: ObjectReference, field: Field) =
|
||||
(value.getValue(field) as LongValue).longValue()
|
||||
fun objectValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
|
||||
value?.let {
|
||||
method?.let {
|
||||
context.invokeMethodAsObject(value, method, *values)
|
||||
}
|
||||
}
|
||||
|
||||
fun longValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
|
||||
method?.let { (context.invokeMethod(value, it, values.asList()) as? LongValue)?.longValue() }
|
||||
|
||||
fun intValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
|
||||
method?.let { (context.invokeMethod(value, it, values.asList()) as? IntegerValue)?.intValue() }
|
||||
|
||||
fun booleanValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value): Boolean? {
|
||||
value ?: return null
|
||||
method ?: return null
|
||||
return (context.invokeMethod(value, method, values.asList()) as? BooleanValue)?.booleanValue()
|
||||
}
|
||||
|
||||
fun objectValue(value: ObjectReference, field: Field?) =
|
||||
field?.let { value.getValue(it) as ObjectReference? }
|
||||
|
||||
fun intValue(value: ObjectReference, field: Field?) =
|
||||
field?.let { (value.getValue(it) as? IntegerValue)?.intValue() }
|
||||
|
||||
fun longValue(value: ObjectReference, field: Field?) =
|
||||
field?.let { (value.getValue(it) as? LongValue)?.longValue() }
|
||||
|
||||
fun booleanValue(value: ObjectReference?, field: Field?) =
|
||||
field?.let { (value?.getValue(field) as? BooleanValue)?.booleanValue() }
|
||||
|
||||
protected abstract fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): T?
|
||||
}
|
||||
@@ -65,8 +113,8 @@ 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")
|
||||
private val stateFieldRef = makeField("_state") // childContinuation
|
||||
private val contextFieldRef = makeField("context")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStandaloneCoroutine {
|
||||
val state = objectValue(value, stateFieldRef)
|
||||
@@ -76,6 +124,15 @@ class StandaloneCoroutine(context: DefaultExecutionContext) :
|
||||
return MirrorOfStandaloneCoroutine(value, childcontinuation, coroutineContext)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun instance(context: DefaultExecutionContext): StandaloneCoroutine? {
|
||||
val sc = StandaloneCoroutine(context)
|
||||
if (sc.cls == null)
|
||||
return null
|
||||
else
|
||||
return sc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MirrorOfStandaloneCoroutine(
|
||||
@@ -87,7 +144,7 @@ data class MirrorOfStandaloneCoroutine(
|
||||
class ChildContinuation(context: DefaultExecutionContext) :
|
||||
BaseMirror<MirrorOfChildContinuation>("kotlinx.coroutines.ChildContinuation", context) {
|
||||
private val childContinuationMirror = CancellableContinuationImpl(context)
|
||||
private val childFieldRef: Field = makeField("child") // cancellableContinuationImpl
|
||||
private val childFieldRef = makeField("child") // cancellableContinuationImpl
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfChildContinuation? {
|
||||
val child = objectValue(value, childFieldRef)
|
||||
@@ -104,11 +161,11 @@ 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")
|
||||
private val decisionFieldRef = makeField("_decision")
|
||||
private val delegateFieldRef = makeField("delegate") // DispatchedContinuation
|
||||
private val resumeModeFieldRef = makeField("resumeMode")
|
||||
private val submissionTimeFieldRef = makeField("submissionTime")
|
||||
private val contextFieldRef = makeField("context")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCancellableContinuationImpl? {
|
||||
val decision = intValue(value, decisionFieldRef)
|
||||
@@ -123,16 +180,16 @@ class CancellableContinuationImpl(context: DefaultExecutionContext) :
|
||||
|
||||
data class MirrorOfCancellableContinuationImpl(
|
||||
val that: ObjectReference,
|
||||
val decision: Int,
|
||||
val decision: Int?,
|
||||
val delegate: MirrorOfDispatchedContinuation?,
|
||||
val resumeMode: Int,
|
||||
val submissionTyme: Long,
|
||||
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")
|
||||
private val decisionFieldRef = makeField("continuation")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDispatchedContinuation? {
|
||||
val continuation = objectValue(value, decisionFieldRef)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.ArrayReference
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.sun.jdi.StringReference
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
|
||||
class DebugMetadata private constructor(context: DefaultExecutionContext) :
|
||||
BaseMirror<MirrorOfDebugProbesImpl>("kotlin.coroutines.jvm.internal.DebugMetadataKt", context) {
|
||||
private val getStackTraceElementMethod = makeMethod("getStackTraceElement")
|
||||
private val getSpilledVariableFieldMappingMethod =
|
||||
makeMethod("getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;")
|
||||
val baseContinuationImpl = BaseContinuationImpl(context, this)
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfDebugProbesImpl? =
|
||||
throw IllegalStateException("Not meant to be mirrored.")
|
||||
|
||||
fun fetchContinuationStack(continuation: ObjectReference, context: DefaultExecutionContext): MirrorOfContinuationStack {
|
||||
val coroutineStack = mutableListOf<MirrorOfStackFrame>()
|
||||
var loopContinuation: ObjectReference? = continuation
|
||||
while (loopContinuation != null) {
|
||||
val continuationMirror = baseContinuationImpl.mirror(loopContinuation, context) ?: break
|
||||
coroutineStack.add(MirrorOfStackFrame(loopContinuation, continuationMirror))
|
||||
loopContinuation = continuationMirror.nextContinuation
|
||||
}
|
||||
return MirrorOfContinuationStack(continuation, coroutineStack)
|
||||
}
|
||||
|
||||
fun getStackTraceElement(value: ObjectReference, context: DefaultExecutionContext) =
|
||||
staticMethodValue(getStackTraceElementMethod, context, value)
|
||||
|
||||
fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext) =
|
||||
staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference
|
||||
|
||||
companion object {
|
||||
val log by logger
|
||||
|
||||
fun instance(context: DefaultExecutionContext): DebugMetadata? {
|
||||
try {
|
||||
return DebugMetadata(context)
|
||||
} catch (e: IllegalStateException) {
|
||||
log.warn("Attempt to access DebugMetadata", e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MirrorOfContinuationStack(val that: ObjectReference, val coroutineStack: List<MirrorOfStackFrame>)
|
||||
|
||||
data class MirrorOfStackFrame(
|
||||
val that: ObjectReference,
|
||||
val baseContinuationImpl: MirrorOfBaseContinuationImpl
|
||||
)
|
||||
|
||||
data class FieldVariable(val fieldName: String, val variableName: String)
|
||||
|
||||
class BaseContinuationImpl(context: DefaultExecutionContext, private val debugMetadata: DebugMetadata) :
|
||||
BaseMirror<MirrorOfBaseContinuationImpl>("kotlin.coroutines.jvm.internal.BaseContinuationImpl", context) {
|
||||
|
||||
private val getCompletion = makeMethod("getCompletion", "()Lkotlin/coroutines/Continuation;")
|
||||
private val stackTraceElement = StackTraceElement(context)
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfBaseContinuationImpl? {
|
||||
val stackTraceObjectReference = debugMetadata.getStackTraceElement(value, context) ?: return null
|
||||
val stackTraceElementMirror = stackTraceElement.mirror(stackTraceObjectReference, context)
|
||||
val fieldVariables = getSpilledVariableFieldMapping(value, context)
|
||||
val completionValue = objectValue(value, getCompletion, context)
|
||||
val completion = if (completionValue != null && isCompatible(completionValue)) completionValue else null
|
||||
val coroutineOwner = if (completionValue != null && DebugProbesImpl_CoroutineOwner.instanceOf(completionValue)) completionValue else null
|
||||
return MirrorOfBaseContinuationImpl(value, stackTraceElementMirror, fieldVariables, completion, coroutineOwner)
|
||||
}
|
||||
|
||||
private fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext): List<FieldVariable> {
|
||||
val getSpilledVariableFieldMappingReference =
|
||||
debugMetadata.getSpilledVariableFieldMapping(value, context) ?: return emptyList()
|
||||
|
||||
val length = getSpilledVariableFieldMappingReference.length() / 2
|
||||
val fieldVariables = ArrayList<FieldVariable>()
|
||||
for (index in 0 until length) {
|
||||
var fieldVariable = getFieldVariableName(getSpilledVariableFieldMappingReference, index) ?: continue
|
||||
fieldVariables.add(fieldVariable)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
data class MirrorOfBaseContinuationImpl(
|
||||
val that: ObjectReference,
|
||||
val stackTraceElement: MirrorOfStackTraceElement?,
|
||||
val fieldVariables: List<FieldVariable>,
|
||||
val nextContinuation: ObjectReference?,
|
||||
val coroutineOwner: ObjectReference?
|
||||
)
|
||||
@@ -28,9 +28,6 @@ class JavaLangMirror(context: DefaultExecutionContext) {
|
||||
// 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()
|
||||
|
||||
@@ -63,3 +60,34 @@ class JavaLangMirror(context: DefaultExecutionContext) {
|
||||
private fun fetchClassName(instance: ObjectReference) =
|
||||
(instance.getValue(declaringClassFieldRef) as? StringReference)?.value() ?: ""
|
||||
}
|
||||
|
||||
class JavaUtilAbstractCollection(context: DefaultExecutionContext) :
|
||||
BaseMirror<MirrorOfJavaLangAbstractCollection>("java.util.AbstractCollection", context) {
|
||||
val abstractList = JavaUtilAbstractList(context)
|
||||
val sizeMethod = makeMethod("size")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfJavaLangAbstractCollection? {
|
||||
val list = mutableListOf<ObjectReference>()
|
||||
val size = intValue(value, sizeMethod, context) ?: 0
|
||||
for (index in 0 until size) {
|
||||
val reference = abstractList.get(value, index, context) ?: continue
|
||||
list.add(reference)
|
||||
}
|
||||
return MirrorOfJavaLangAbstractCollection(value, list)
|
||||
}
|
||||
}
|
||||
|
||||
class JavaUtilAbstractList(context: DefaultExecutionContext) :
|
||||
BaseMirror<ObjectReference>("java.util.AbstractList", context) {
|
||||
val getMethod = makeMethod("get")
|
||||
|
||||
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext) =
|
||||
null
|
||||
|
||||
fun get(value: ObjectReference, index: Int, context: DefaultExecutionContext): ObjectReference? =
|
||||
objectValue(value, getMethod, context, context.vm.mirrorOf(index))
|
||||
}
|
||||
|
||||
|
||||
data class MirrorOfJavaLangAbstractCollection(val that: ObjectReference, val values: List<ObjectReference>)
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||
import com.sun.jdi.ObjectReference
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ContinuationHolder
|
||||
import java.lang.Integer.min
|
||||
|
||||
|
||||
class CoroutineFrameBuilder {
|
||||
|
||||
companion object {
|
||||
val log by logger
|
||||
const val PRE_FETCH_FRAME_COUNT = 5
|
||||
|
||||
fun build(coroutine: CoroutineInfoData, suspendContext: SuspendContextImpl): DoubleFrameList? =
|
||||
when {
|
||||
coroutine.isRunning() -> buildStackFrameForActive(coroutine, suspendContext)
|
||||
coroutine.isSuspended() -> DoubleFrameList(coroutine.stackTrace, coroutine.creationStackTrace)
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun buildStackFrameForActive(coroutine: CoroutineInfoData, suspendContext: SuspendContextImpl): DoubleFrameList? {
|
||||
val activeThread = coroutine.activeThread ?: return null
|
||||
|
||||
val coroutineStackFrameList = mutableListOf<CoroutineStackFrameItem>()
|
||||
val threadReferenceProxyImpl = ThreadReferenceProxyImpl(suspendContext.debugProcess.virtualMachineProxy, activeThread)
|
||||
val realFrames = threadReferenceProxyImpl.forceFrames()
|
||||
for (runningStackFrameProxy in realFrames) {
|
||||
val preflightStackFrame = coroutineExitFrame(runningStackFrameProxy, suspendContext)
|
||||
if (preflightStackFrame != null) {
|
||||
buildRealStackFrameItem(preflightStackFrame.stackFrameProxy)?.let {
|
||||
coroutineStackFrameList.add(it)
|
||||
}
|
||||
|
||||
val doubleFrameList = build(preflightStackFrame, suspendContext)
|
||||
coroutineStackFrameList.addAll(doubleFrameList.stackTrace)
|
||||
return DoubleFrameList(coroutineStackFrameList, doubleFrameList.creationStackTrace)
|
||||
} else {
|
||||
buildRealStackFrameItem(runningStackFrameProxy)?.let {
|
||||
coroutineStackFrameList.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return DoubleFrameList(coroutineStackFrameList, emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by CoroutineAsyncStackTraceProvider to build XFramesView
|
||||
*/
|
||||
fun build(preflightFrame: CoroutinePreflightStackFrame, suspendContext: SuspendContextImpl): DoubleFrameList {
|
||||
val stackFrames = mutableListOf<CoroutineStackFrameItem>()
|
||||
|
||||
stackFrames.addAll(preflightFrame.restoredStackTrace())
|
||||
|
||||
// rest of the stack
|
||||
// @TODO perhaps we need to merge the dropped frame below with the last restored (at least variables).
|
||||
val framesLeft = preflightFrame.threadPreCoroutineFrames.drop(1)
|
||||
stackFrames.addAll(framesLeft.mapIndexedNotNull { index, stackFrameProxyImpl ->
|
||||
suspendContext.invokeInManagerThread { buildRealStackFrameItem(stackFrameProxyImpl) }
|
||||
})
|
||||
|
||||
return DoubleFrameList(stackFrames, preflightFrame.coroutineInfoData.creationStackTrace)
|
||||
}
|
||||
|
||||
data class DoubleFrameList(
|
||||
val stackTrace: List<CoroutineStackFrameItem>,
|
||||
val creationStackTrace: List<CreationCoroutineStackFrameItem>
|
||||
)
|
||||
|
||||
private fun buildRealStackFrameItem(
|
||||
frame: StackFrameProxyImpl
|
||||
): RunningCoroutineStackFrameItem? {
|
||||
val location = frame.location()
|
||||
if (!location.safeCoroutineExitPointLineNumber())
|
||||
return RunningCoroutineStackFrameItem(frame, location)
|
||||
else
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by CoroutineStackFrameInterceptor to check if that frame is 'exit' coroutine frame.
|
||||
*/
|
||||
fun coroutineExitFrame(
|
||||
frame: StackFrameProxyImpl,
|
||||
suspendContext: SuspendContextImpl
|
||||
): CoroutinePreflightStackFrame? {
|
||||
return suspendContext.invokeInManagerThread {
|
||||
val sem = frame.location().isPreFlight()
|
||||
if (sem.isCoroutineFound()) {
|
||||
lookupContinuation(suspendContext, frame, sem)
|
||||
} else
|
||||
null
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun lookupContinuation(
|
||||
suspendContext: SuspendContextImpl,
|
||||
frame: StackFrameProxyImpl,
|
||||
mode: SuspendExitMode
|
||||
): CoroutinePreflightStackFrame? {
|
||||
if (!mode.isCoroutineFound())
|
||||
return null
|
||||
|
||||
val theFollowingFrames = theFollowingFrames(frame) ?: emptyList()
|
||||
val suspendParameterFrame = if (mode.isSuspendMethodParameter()) {
|
||||
if (theFollowingFrames.isNotEmpty()) {
|
||||
// have to check next frame if that's invokeSuspend:-1 before proceed, otherwise skip
|
||||
lookForTheFollowingFrame(theFollowingFrames) ?: return null
|
||||
} else
|
||||
return null
|
||||
} else
|
||||
null
|
||||
|
||||
if (threadAndContextSupportsEvaluation(suspendContext, frame)) {
|
||||
val context = suspendContext.executionContext() ?: return null
|
||||
val continuation = when (mode) {
|
||||
SuspendExitMode.SUSPEND_LAMBDA -> getThisContinuation(frame)
|
||||
SuspendExitMode.SUSPEND_METHOD_PARAMETER -> getLVTContinuation(frame)
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
val continuationHolder = ContinuationHolder.instance(context) ?: return null
|
||||
val coroutineInfo = continuationHolder.extractCoroutineInfoData(continuation) ?: return null
|
||||
return preflight(frame, theFollowingFrames, coroutineInfo, mode)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun lookForTheFollowingFrame(theFollowingFrames: List<StackFrameProxyImpl>): StackFrameProxyImpl? {
|
||||
for (i in 0 until min(PRE_FETCH_FRAME_COUNT, theFollowingFrames.size)) { // pre-scan PRE_FETCH_FRAME_COUNT frames
|
||||
val nextFrame = theFollowingFrames.get(i)
|
||||
if (nextFrame.location().isPreFlight() == SuspendExitMode.SUSPEND_METHOD) {
|
||||
return nextFrame
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun preflight(
|
||||
frame: StackFrameProxyImpl,
|
||||
framesLeft: List<StackFrameProxyImpl>,
|
||||
coroutineInfoData: CoroutineInfoData,
|
||||
mode: SuspendExitMode
|
||||
): CoroutinePreflightStackFrame? {
|
||||
val descriptor = StackFrameDescriptorImpl(frame, MethodsTracker())
|
||||
return CoroutinePreflightStackFrame(
|
||||
coroutineInfoData,
|
||||
descriptor,
|
||||
framesLeft,
|
||||
mode
|
||||
)
|
||||
}
|
||||
|
||||
private fun getLVTContinuation(frame: StackFrameProxyImpl?) =
|
||||
frame?.continuationVariableValue()
|
||||
|
||||
private fun getThisContinuation(frame: StackFrameProxyImpl?): ObjectReference? =
|
||||
frame?.thisVariableValue()
|
||||
|
||||
fun theFollowingFrames(frame: StackFrameProxyImpl): List<StackFrameProxyImpl>? {
|
||||
val frames = frame.threadProxy().frames()
|
||||
val indexOfCurrentFrame = frames.indexOf(frame)
|
||||
if (indexOfCurrentFrame >= 0) {
|
||||
val indexOfGetCoroutineSuspended = hasGetCoroutineSuspended(frames)
|
||||
// @TODO if found - skip this thread stack
|
||||
if (indexOfGetCoroutineSuspended < 0 && frames.size > indexOfCurrentFrame + 1)
|
||||
return frames.drop(indexOfCurrentFrame + 1)
|
||||
} else {
|
||||
log.error("Frame isn't found on the thread stack.")
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,11 @@
|
||||
* 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
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.util
|
||||
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
@@ -17,27 +18,44 @@ 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.coroutine.data.SuspendExitMode
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
|
||||
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
|
||||
|
||||
const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as creation stacktrace separator in kotlinx.coroutines
|
||||
|
||||
fun Method.isInvokeSuspend(): Boolean =
|
||||
name() == "invokeSuspend" && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;"
|
||||
|
||||
fun Method.isInvoke(): Boolean =
|
||||
name() == "invoke" && signature().contains("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.hasContinuationParameter() =
|
||||
signature().contains("Lkotlin/coroutines/Continuation;)")
|
||||
|
||||
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 Location.isPreFlight(): SuspendExitMode {
|
||||
val method = safeMethod() ?: return SuspendExitMode.NONE
|
||||
if (method.isSuspendLambda())
|
||||
return SuspendExitMode.SUSPEND_LAMBDA
|
||||
else if (method.hasContinuationParameter())
|
||||
return SuspendExitMode.SUSPEND_METHOD_PARAMETER
|
||||
else if ((method.isInvokeSuspend() || method.isInvoke()) && safeCoroutineExitPointLineNumber())
|
||||
return SuspendExitMode.SUSPEND_METHOD
|
||||
return SuspendExitMode.NONE
|
||||
}
|
||||
|
||||
fun Location.safeCoroutineExitPointLineNumber() =
|
||||
wrapIllegalArgumentException { DebuggerUtilsEx.getLineNumber(this, false) } ?: -2 == -1
|
||||
|
||||
fun ReferenceType.isContinuation() =
|
||||
isBaseContinuationImpl() || isSubtype("kotlin.coroutines.Continuation")
|
||||
|
||||
@@ -53,8 +71,8 @@ fun Type.isSubTypeOrSame(className: String) =
|
||||
fun ReferenceType.isSuspendLambda() =
|
||||
SUSPEND_LAMBDA_CLASSES.any { isSubtype(it) }
|
||||
|
||||
fun Location.isPreExitFrame() =
|
||||
safeMethod()?.isResumeWith() ?: false
|
||||
fun Location.isInvokeSuspend() =
|
||||
safeMethod()?.isInvokeSuspend() ?: false
|
||||
|
||||
fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
|
||||
val continuationVariable = safeVisibleVariableByName(variableName) ?: return null
|
||||
@@ -64,26 +82,29 @@ fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
|
||||
fun StackFrameProxyImpl.completionVariableValue(): ObjectReference? =
|
||||
variableValue("completion")
|
||||
|
||||
fun StackFrameProxyImpl.continuationVariableValue(): ObjectReference? =
|
||||
variableValue("\$continuation")
|
||||
|
||||
fun StackFrameProxyImpl.thisVariableValue(): ObjectReference? =
|
||||
this.thisObject()
|
||||
|
||||
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>) =
|
||||
fun hasGetCoroutineSuspended(frames: List<StackFrameProxyImpl>) =
|
||||
frames.indexOfFirst { it.safeLocation()?.safeMethod()?.isGetCOROUTINE_SUSPENDED() == true }
|
||||
|
||||
fun StackTraceElement.isCreationSeparatorFrame() =
|
||||
className.startsWith(CoroutineBuilder.CREATION_STACK_TRACE_SEPARATOR)
|
||||
className.startsWith(CREATION_STACK_TRACE_SEPARATOR)
|
||||
|
||||
fun StackTraceElement.findPosition(project: Project): XSourcePosition? =
|
||||
getPosition(project, className, lineNumber)
|
||||
@@ -92,7 +113,7 @@ fun Location.findPosition(project: Project) =
|
||||
getPosition(project, declaringType().name(), lineNumber())
|
||||
|
||||
fun ClassType.completionField() =
|
||||
fieldByName("completion") as Field?
|
||||
fieldByName("completion")
|
||||
|
||||
private fun getPosition(project: Project, className: String, lineNumber: Int): XSourcePosition? {
|
||||
val psiFacade = JavaPsiFacade.getInstance(project)
|
||||
@@ -105,17 +126,6 @@ private fun getPosition(project: Project, className: String, lineNumber: Int): X
|
||||
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)) }
|
||||
@@ -131,3 +141,8 @@ fun SuspendContextImpl.supportsEvaluation() =
|
||||
|
||||
fun XDebugSession.suspendContextImpl() =
|
||||
suspendContext as SuspendContextImpl
|
||||
|
||||
fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl?) =
|
||||
suspendContext.invokeInManagerThread {
|
||||
suspendContext.supportsEvaluation() && frameProxy?.threadProxy()?.supportsEvaluation() ?: false
|
||||
} ?: false
|
||||
@@ -5,14 +5,24 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.util
|
||||
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
|
||||
fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSourcePosition? {
|
||||
val psiFacade = JavaPsiFacade.getInstance(project)
|
||||
@@ -31,7 +41,27 @@ fun getPosition(stackTraceElement: StackTraceElement, project: Project): XSource
|
||||
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
|
||||
}
|
||||
|
||||
class ProjectNotification(val project: Project) {
|
||||
fun error(message: String) =
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.ERROR).notify(project)
|
||||
fun Location.format(): String {
|
||||
val method = safeMethod()
|
||||
return "${method?.name() ?: "noname"}:${safeLineNumber()}, ${method?.declaringType()?.name() ?: "empty"}"
|
||||
}
|
||||
|
||||
fun JavaStackFrame.format(): String {
|
||||
val location = descriptor.location
|
||||
return location?.let { it.format() } ?: "emptyLocation"
|
||||
}
|
||||
|
||||
fun StackFrameItem.format(): String {
|
||||
val method = this.method()
|
||||
val type = this.path()
|
||||
val lineNumber = this.line()
|
||||
return "$method:$lineNumber, $type"
|
||||
}
|
||||
|
||||
fun StackFrameProxyImpl.format(): String {
|
||||
return safeLocation()?.format() ?: "emptyLocation"
|
||||
}
|
||||
|
||||
fun isInUnitTest() = ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
fun coroutineDebuggerTraceEnabled() = Registry.`is`("kotlin.debugger.coroutines.trace") || isInUnitTest()
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.util
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.DefaultLogger
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.XDebugSessionListener
|
||||
import org.apache.log4j.Level
|
||||
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JComponent
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
@@ -33,8 +37,9 @@ class LoggerDelegate : ReadOnlyProperty<Any, Logger> {
|
||||
lateinit var logger: Logger
|
||||
|
||||
override fun getValue(thisRef: Any, property: KProperty<*>): Logger {
|
||||
if (!::logger.isInitialized)
|
||||
if (!::logger.isInitialized) {
|
||||
logger = Logger.getInstance(thisRef.javaClass)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
val index = selectedIndex
|
||||
if (index >= 0) {
|
||||
val selection = model.getElementAt(index) as CoroutineInfoData
|
||||
AnalyzeStacktraceUtil.printStacktrace(consoleView, selection.stringStackTrace)
|
||||
AnalyzeStacktraceUtil.printStacktrace(consoleView, stringStackTrace(selection))
|
||||
} else {
|
||||
AnalyzeStacktraceUtil.printStacktrace(consoleView, "")
|
||||
}
|
||||
@@ -144,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.key.name, text)) {
|
||||
if (StringUtil.containsIgnoreCase(stringStackTrace(state), text) || StringUtil.containsIgnoreCase(state.key.name, text)) {
|
||||
model.addElement(state)
|
||||
if (selection === state) {
|
||||
selectedIndex = index
|
||||
@@ -263,7 +263,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
val buf = StringBuilder()
|
||||
buf.append(KotlinDebuggerCoroutinesBundle.message("coroutine.dump.full.title")).append("\n\n")
|
||||
for (state in myCoroutinesDump) {
|
||||
buf.append(state.stringStackTrace).append("\n\n")
|
||||
buf.append(stringStackTrace(state)).append("\n\n")
|
||||
}
|
||||
CopyPasteManager.getInstance().setContents(StringSelection(buf.toString()))
|
||||
|
||||
@@ -283,7 +283,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
|
||||
override fun getReportText() = buildString {
|
||||
for (state in infoData)
|
||||
append(state.stringStackTrace).append("\n\n")
|
||||
append(stringStackTrace(state)).append("\n\n")
|
||||
}
|
||||
|
||||
override fun getDefaultFilePath() = (myProject.basePath ?: "") + File.separator + defaultReportFileName
|
||||
@@ -292,4 +292,13 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
|
||||
private val defaultReportFileName = "coroutines_report.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stringStackTrace(info: CoroutineInfoData) =
|
||||
buildString {
|
||||
appendln("\"${info.key.name}\", state: ${info.key.state}")
|
||||
info.stackTrace.forEach {
|
||||
appendln("\t$it")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ class SimpleColoredTextIconPresentationRenderer {
|
||||
val hasChildren = infoData.stackTrace.isNotEmpty() || infoData.creationStackTrace.isNotEmpty()
|
||||
val label = SimpleColoredTextIcon(icon, hasChildren)
|
||||
label.append("\"")
|
||||
label.appendValue(infoData.key.name)
|
||||
label.appendValue(infoData.key.formatName())
|
||||
label.append("\": ${infoData.key.state}")
|
||||
if (name.isNotEmpty()) {
|
||||
label.append(" on thread \"")
|
||||
@@ -181,7 +181,7 @@ class SimpleColoredTextIconPresentationRenderer {
|
||||
SimpleColoredTextIcon(
|
||||
AllIcons.Debugger.ThreadSuspended,
|
||||
true,
|
||||
KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.frame", infoData.key.name)
|
||||
KotlinDebuggerCoroutinesBundle.message("coroutine.dump.creation.trace")
|
||||
)
|
||||
|
||||
fun renderErrorNode(error: String) =
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.view
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.JavaExecutionStack
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.ide.CommonActionsManager
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
@@ -35,6 +32,7 @@ import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder
|
||||
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
|
||||
@@ -57,8 +55,6 @@ 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 =
|
||||
// val debugProcess: DebugProcessImpl = javaDebugProcess.debuggerSession.process
|
||||
val renderer = SimpleColoredTextIconPresentationRenderer()
|
||||
val managerThreadExecutor = ManagerThreadExecutor(session)
|
||||
var treeState: XDebuggerTreeState? = null
|
||||
@@ -149,14 +145,32 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
inner class XCoroutinesRootNode(suspendContext: SuspendContextImpl) :
|
||||
XValueContainerNode<CoroutineGroupContainer>(
|
||||
panel.tree, null, false,
|
||||
CoroutineGroupContainer(suspendContext, KotlinDebuggerCoroutinesBundle.message("coroutine.view.default.group"))
|
||||
CoroutineGroupContainer(suspendContext)
|
||||
)
|
||||
|
||||
inner class CoroutineGroupContainer(val suspendContext: SuspendContextImpl, val groupName: String) : XValueContainer() {
|
||||
inner class CoroutineGroupContainer(val suspendContext: SuspendContextImpl) : XValueContainer() {
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
val groups = XValueChildrenList.singleton(CoroutineContainer(suspendContext, groupName))
|
||||
node.addChildren(groups, true)
|
||||
managerThreadExecutor.on(suspendContext).invoke {
|
||||
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
|
||||
|
||||
val emptyDispatcherName = KotlinDebuggerCoroutinesBundle.message("coroutine.view.dispatcher.empty")
|
||||
var coroutineCache = debugProbesProxy.dumpCoroutines()
|
||||
if (coroutineCache.isOk()) {
|
||||
val children = XValueChildrenList()
|
||||
var groups = coroutineCache.cache.groupBy { it.key.dispatcher }
|
||||
for (dispatcher in groups.keys) {
|
||||
children.add(CoroutineContainer(suspendContext, dispatcher ?: emptyDispatcherName, groups[dispatcher]))
|
||||
}
|
||||
if (children.size() > 0)
|
||||
node.addChildren(children, true)
|
||||
else
|
||||
node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true)
|
||||
} else {
|
||||
val errorNode = ErrorNode("coroutine.view.fetching.error")
|
||||
node.addChildren(XValueChildrenList.singleton(errorNode), true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.addChildren(
|
||||
XValueChildrenList.singleton(ErrorNode("to.enable.information.breakpoint.suspend.policy.should.be.set.to.all.threads")),
|
||||
@@ -168,28 +182,21 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
|
||||
inner class CoroutineContainer(
|
||||
val suspendContext: SuspendContextImpl,
|
||||
val groupName: String
|
||||
val groupName: String,
|
||||
val coroutines: List<CoroutineInfoData>?
|
||||
) : RendererContainer(renderer.renderGroup(groupName)) {
|
||||
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
managerThreadExecutor.on(suspendContext).schedule {
|
||||
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
|
||||
|
||||
var coroutineCache = debugProbesProxy.dumpCoroutines()
|
||||
if (coroutineCache.isOk()) {
|
||||
val children = XValueChildrenList()
|
||||
for (coroutineInfo in coroutineCache.cache) {
|
||||
children.add(FramesContainer(coroutineInfo, suspendContext))
|
||||
}
|
||||
if (children.size() > 0)
|
||||
node.addChildren(children, true)
|
||||
else
|
||||
node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true)
|
||||
} else {
|
||||
val errorNode = ErrorNode("coroutine.view.fetching.error")
|
||||
node.addChildren(XValueChildrenList.singleton(errorNode), true)
|
||||
val children = XValueChildrenList()
|
||||
if (coroutines != null)
|
||||
for (coroutineInfo in coroutines) {
|
||||
children.add(FramesContainer(coroutineInfo, suspendContext))
|
||||
}
|
||||
}
|
||||
if (children.size() > 0)
|
||||
node.addChildren(children, true)
|
||||
else
|
||||
node.addChildren(XValueChildrenList.singleton(InfoNode("coroutine.view.fetching.not_found")), true)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,19 +210,15 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
|
||||
) : RendererContainer(renderer.render(infoData)) {
|
||||
|
||||
override fun computeChildren(node: XCompositeNode) {
|
||||
managerThreadExecutor.on(suspendContext).schedule {
|
||||
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
|
||||
managerThreadExecutor.on(suspendContext).invoke {
|
||||
val children = XValueChildrenList()
|
||||
var stackFrames = debugProbesProxy.frameBuilder().build(infoData)
|
||||
val creationStack = mutableListOf<CreationCoroutineStackFrameItem>()
|
||||
for (frame in stackFrames) {
|
||||
if (frame is CreationCoroutineStackFrameItem)
|
||||
creationStack.add(frame)
|
||||
else if (frame is CoroutineStackFrameItem)
|
||||
children.add(CoroutineFrameValue(infoData, frame))
|
||||
val doubleFrameList = CoroutineFrameBuilder.build(infoData, suspendContext)
|
||||
doubleFrameList?.stackTrace?.forEach {
|
||||
children.add(CoroutineFrameValue(infoData, it))
|
||||
}
|
||||
doubleFrameList?.creationStackTrace?.let {
|
||||
children.add(CreationFramesContainer(infoData, it))
|
||||
}
|
||||
if (creationStack.isNotEmpty())
|
||||
children.add(CreationFramesContainer(infoData, creationStack))
|
||||
node.addChildren(children, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ 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
|
||||
@@ -17,12 +15,11 @@ 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.coroutine.util.findPosition
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.invokeInManagerThread
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.suspendContextImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
@@ -61,43 +58,50 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
|
||||
is RunningCoroutineStackFrameItem -> {
|
||||
val threadProxy = stackFrameItem.frame.threadProxy()
|
||||
val isCurrentContext = suspendContext.thread == threadProxy
|
||||
val executionStack = JavaExecutionStack(
|
||||
val executionStack = suspendContext.invokeInManagerThread { JavaExecutionStack(
|
||||
threadProxy,
|
||||
debugProcess,
|
||||
isCurrentContext
|
||||
)
|
||||
val jStackFrame = executionStack.createStackFrame(stackFrameItem.frame)
|
||||
createStackAndSetFrame(threadProxy, { jStackFrame }, isCurrentContext)
|
||||
isCurrentContext) } ?: return false
|
||||
createStackAndSetFrame(threadProxy, { executionStack.createStackFrame(stackFrameItem.frame) }, 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)
|
||||
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
||||
SyntheticStackFrame(stackFrameItem.descriptor(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) })
|
||||
val position = stackFrameItem.location.findPosition(session.project)
|
||||
?: return false
|
||||
|
||||
createStackAndSetFrame(threadProxy, {
|
||||
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
||||
SyntheticStackFrame(stackFrameItem.descriptor(realFrame), stackFrameItem.spilledVariables, position)
|
||||
})
|
||||
}
|
||||
is RestoredCoroutineStackFrameItem -> {
|
||||
val threadProxy = stackFrameItem.frame.threadProxy()
|
||||
val position = stackFrameItem.location.findPosition(session.project)
|
||||
?: return false
|
||||
createStackAndSetFrame(threadProxy, {
|
||||
SyntheticStackFrame(stackFrameItem.emptyDescriptor(), stackFrameItem.spilledVariables, position)
|
||||
SyntheticStackFrame(stackFrameItem.descriptor(), stackFrameItem.spilledVariables, position)
|
||||
})
|
||||
}
|
||||
is DefaultCoroutineStackFrameItem -> {
|
||||
is DefaultCoroutineStackFrameItem, is SuspendCoroutineStackFrameItem -> {
|
||||
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)
|
||||
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
|
||||
val descriptor = when (stackFrameItem) {
|
||||
is DefaultCoroutineStackFrameItem -> stackFrameItem.descriptor(realFrame)
|
||||
is SuspendCoroutineStackFrameItem -> stackFrameItem.descriptor(realFrame)
|
||||
else -> null
|
||||
} ?: return@createStackAndSetFrame null
|
||||
SyntheticStackFrame(descriptor, stackFrameItem.spilledVariables, position)
|
||||
})
|
||||
}
|
||||
else -> {
|
||||
@@ -123,39 +127,18 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
|
||||
fun setCurrentStackFrame(stackFrameStack: XStackFrameStack) {
|
||||
applicationThreadExecutor.schedule(
|
||||
{
|
||||
session.setCurrentStackFrame(stackFrameStack.executionStack, stackFrameStack.stackFrame)
|
||||
session.setCurrentStackFrame(stackFrameStack.executionStack, stackFrameStack.stackFrame, false)
|
||||
}, tree
|
||||
)
|
||||
}
|
||||
|
||||
data class XStackFrameStack(val stackFrame: XStackFrame, val executionStack: XExecutionStack);
|
||||
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?) {
|
||||
|
||||
@@ -6,15 +6,12 @@
|
||||
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
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import io.ktor.util.findAllSupertypes
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.CoroutineFrameBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isPreFlight
|
||||
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.getSafe
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
@@ -28,19 +25,17 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
|
||||
}
|
||||
|
||||
override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) {
|
||||
val asyncStackTraceProvider = getAsyncStackTraceProvider()
|
||||
if (asyncStackTraceProvider == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
doOnBreakpoint {
|
||||
val frameProxy = this.frameProxy
|
||||
if (frameProxy != null) {
|
||||
try {
|
||||
val stackTrace = asyncStackTraceProvider.lookupForResumeContinuation(frameProxy, this)
|
||||
if (stackTrace != null && stackTrace.isNotEmpty()) {
|
||||
print(renderAsyncStackTrace(stackTrace), ProcessOutputTypes.SYSTEM)
|
||||
val sem = frameProxy.location().isPreFlight()
|
||||
val coroutineInfoData = if (sem.isCoroutineFound())
|
||||
CoroutineFrameBuilder.lookupContinuation(this, frameProxy, sem)?.coroutineInfoData
|
||||
else
|
||||
null
|
||||
if (coroutineInfoData != null && coroutineInfoData.stackTrace.isNotEmpty()) {
|
||||
print(renderAsyncStackTrace(coroutineInfoData.stackTrace), ProcessOutputTypes.SYSTEM)
|
||||
} else {
|
||||
println("No async stack trace available", ProcessOutputTypes.SYSTEM)
|
||||
}
|
||||
@@ -57,23 +52,6 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAsyncStackTraceProvider(): CoroutineAsyncStackTraceProvider? {
|
||||
val area = Extensions.getArea(null)
|
||||
if (!area.hasExtensionPoint(ASYNC_STACKTRACE_EP_NAME)) {
|
||||
System.err.println("$ASYNC_STACKTRACE_EP_NAME extension point is not found (probably old IDE version)")
|
||||
return null
|
||||
}
|
||||
|
||||
val extensionPoint = area.getExtensionPoint<Any>(ASYNC_STACKTRACE_EP_NAME)
|
||||
val provider = extensionPoint.extensions.firstIsInstanceOrNull<CoroutineAsyncStackTraceProvider>()
|
||||
|
||||
if (provider == null) {
|
||||
System.err.println("Kotlin coroutine async stack trace provider is not found")
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
private fun Throwable.stackTraceAsString(): String {
|
||||
val writer = StringWriter()
|
||||
printStackTrace(PrintWriter(writer))
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.test
|
||||
|
||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.impl.OutputChecker
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.xdebugger.frame.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.format
|
||||
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
|
||||
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.KotlinOutputChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.XDebuggerTestUtil
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
abstract class AbstractContinuationStackTraceTest : KotlinDescriptorTestCaseWithStackFrames() {
|
||||
override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) {
|
||||
printStackFrame(files, preferences)
|
||||
}
|
||||
}
|
||||
@@ -211,21 +211,6 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDescriptorTestCaseWi
|
||||
super.expandAll(tree, runnable, HashSet(), filter, suspendContext)
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.runActionInSuspendCommand(action: SuspendContextImpl.() -> Unit) {
|
||||
if (myInProgress) {
|
||||
action()
|
||||
} else {
|
||||
val command = object : SuspendContextCommandImpl(this) {
|
||||
override fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
action(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to execute the action inside a command if we aren't already inside it.
|
||||
debuggerSession.process.managerThread?.invoke(command) ?: command.contextAction(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mayThrow(collector: MutableMap<String, Throwable>, expression: String, f: () -> Unit) {
|
||||
try {
|
||||
f()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.test
|
||||
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.PreflightProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.XDebuggerTestUtil
|
||||
|
||||
abstract class AbstractXCoroutinesStackTraceTest : KotlinDescriptorTestCaseWithStackFrames() {
|
||||
override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) {
|
||||
printStackFrame(files, preferences)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/jvm-debugger/jvm-debugger-test/testData/continuation")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class ContinuationStackTraceTestGenerated extends AbstractContinuationStackTraceTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInContinuation() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/jvm-debugger/jvm-debugger-test/testData/continuation"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("suspendFun.kt")
|
||||
public void testSuspendFun() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendLambda.kt")
|
||||
public void testSuspendLambda() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt");
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.LibraryOrderEntry
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.roots.libraries.ui.OrderRoot
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
@@ -39,6 +40,7 @@ class DebuggerTestCompilerFacility(files: List<TestFile>, private val jvmTarget:
|
||||
|
||||
private val mainFiles: TestFilesByLanguage
|
||||
private val libraryFiles: TestFilesByLanguage
|
||||
private val mavenArtifacts = mutableListOf<String>()
|
||||
|
||||
init {
|
||||
val splitFiles = splitByTarget(files)
|
||||
@@ -61,6 +63,15 @@ class DebuggerTestCompilerFacility(files: List<TestFile>, private val jvmTarget:
|
||||
compileLibrary(libraryFiles, srcDir, classesDir)
|
||||
}
|
||||
|
||||
fun addDependencies(libraryPaths: List<String>) {
|
||||
for (libraryPath in libraryPaths) {
|
||||
mavenArtifacts.add(libraryPath)
|
||||
}
|
||||
}
|
||||
|
||||
fun kotlinStdlibInMavenArtifacts() =
|
||||
mavenArtifacts.find { it.contains(Regex("""kotlin-stdlib-\d+\.\d+\.\d+(\-\w+)?""")) }
|
||||
|
||||
fun compileLibrary(srcDir: File, classesDir: File) {
|
||||
compileLibrary(this.libraryFiles, srcDir, classesDir)
|
||||
|
||||
@@ -72,19 +83,22 @@ class DebuggerTestCompilerFacility(files: List<TestFile>, private val jvmTarget:
|
||||
resources.copy(classesDir)
|
||||
(kotlin + java).copy(srcDir)
|
||||
|
||||
if (kotlinStdlibInMavenArtifacts() == null)
|
||||
mavenArtifacts.add(kotlinStdlibPath)
|
||||
|
||||
if (kotlin.isNotEmpty()) {
|
||||
MockLibraryUtil.compileKotlin(
|
||||
srcDir.absolutePath,
|
||||
classesDir,
|
||||
listOf("-jvm-target", jvmTarget.description),
|
||||
kotlinStdlibPath
|
||||
*(mavenArtifacts.toTypedArray())
|
||||
)
|
||||
}
|
||||
|
||||
if (java.isNotEmpty()) {
|
||||
CodegenTestUtil.compileJava(
|
||||
java.map { File(srcDir, it.name).absolutePath },
|
||||
listOf(kotlinStdlibPath, classesDir.absolutePath),
|
||||
mavenArtifacts + classesDir.absolutePath,
|
||||
listOf("-g"),
|
||||
classesDir
|
||||
)
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.intellij.debugger.impl.DescriptorTestCase
|
||||
import com.intellij.debugger.impl.OutputChecker
|
||||
import com.intellij.execution.configurations.JavaParameters
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.jarRepository.JarRepositoryManager
|
||||
import com.intellij.jarRepository.RemoteRepositoryDescription
|
||||
import com.intellij.openapi.roots.LibraryOrderEntry
|
||||
import com.intellij.openapi.roots.ModifiableRootModel
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
@@ -21,6 +23,7 @@ import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.testFramework.EdtTestUtil
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import org.jetbrains.idea.maven.aether.ArtifactKind
|
||||
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
@@ -38,6 +41,7 @@ import org.jetbrains.kotlin.test.isIgnoredInDatabaseWithLog
|
||||
import org.jetbrains.kotlin.test.testFramework.runWriteAction
|
||||
import org.junit.ComparisonFailure
|
||||
import java.io.File
|
||||
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor as JpsMavenRepositoryLibraryDescriptor
|
||||
|
||||
internal const val KOTLIN_LIBRARY_NAME = "KotlinLibrary"
|
||||
internal const val TEST_LIBRARY_NAME = "TestLibrary"
|
||||
@@ -109,7 +113,10 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() {
|
||||
val compilerFacility = DebuggerTestCompilerFacility(testFiles, jvmTarget)
|
||||
|
||||
for (library in preferences[DebuggerPreferenceKeys.ATTACH_LIBRARY]) {
|
||||
compilerFacility.compileExternalLibrary(library, librarySrcDirectory, libraryOutputDirectory)
|
||||
if (library.startsWith("maven("))
|
||||
addMavenDependency(compilerFacility, library)
|
||||
else
|
||||
compilerFacility.compileExternalLibrary(library, librarySrcDirectory, libraryOutputDirectory)
|
||||
}
|
||||
|
||||
compilerFacility.compileLibrary(librarySrcDirectory, libraryOutputDirectory)
|
||||
@@ -125,6 +132,9 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() {
|
||||
doMultiFileTest(testFiles, preferences)
|
||||
}
|
||||
|
||||
open fun addMavenDependency(compilerFacility: DebuggerTestCompilerFacility, library: String) {
|
||||
}
|
||||
|
||||
private fun createTestFiles(wholeFile: File, wholeFileContents: String): TestFiles {
|
||||
val testFiles = org.jetbrains.kotlin.test.TestFiles.createTestFiles(
|
||||
wholeFile.name,
|
||||
@@ -227,6 +237,5 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase() {
|
||||
return super.shouldRunTest() && !isIgnoredInDatabaseWithLog(this)
|
||||
}
|
||||
|
||||
private fun getTestDirectoryPath(): String = javaClass.getAnnotation(TestMetadata::class.java).value
|
||||
protected fun getTestDirectoryPath(): String = javaClass.getAnnotation(TestMetadata::class.java).value
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.test
|
||||
|
||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.execution.configurations.JavaParameters
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.jarRepository.JarRepositoryManager
|
||||
import com.intellij.jarRepository.RemoteRepositoryDescription
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.libraries.ui.OrderRoot
|
||||
import com.intellij.openapi.roots.ui.configuration.libraryEditor.NewLibraryEditor
|
||||
import com.intellij.testFramework.EdtTestUtil
|
||||
import com.intellij.xdebugger.frame.XNamedValue
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import org.jetbrains.idea.maven.aether.ArtifactKind
|
||||
import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.PreflightProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CreationCoroutineStackFrameItem
|
||||
import org.jetbrains.kotlin.idea.debugger.invokeInSuspendManagerThread
|
||||
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.XDebuggerTestUtil
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.test.testFramework.runWriteAction
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
abstract class KotlinDescriptorTestCaseWithStackFrames() : KotlinDescriptorTestCaseWithStepping() {
|
||||
private companion object {
|
||||
val ASYNC_STACKTRACE_EP_NAME = AsyncStackTraceProvider.EP.name
|
||||
val INDENT_FRAME = 1
|
||||
val INDENT_VARIABLES = 2
|
||||
}
|
||||
|
||||
val agentList = mutableListOf<JpsMavenRepositoryLibraryDescriptor>()
|
||||
val classPath = mutableListOf<String>()
|
||||
|
||||
protected fun out(frame: XStackFrame) {
|
||||
out(INDENT_FRAME, XDebuggerTestUtil.getFramePresentation(frame))
|
||||
outVariables(frame)
|
||||
}
|
||||
|
||||
private fun outVariables(stackFrame: XStackFrame) {
|
||||
val variables = XDebuggerTestUtil.collectChildrenWithError(stackFrame)
|
||||
val sorted = mutableListOf<String>()
|
||||
sorted.addAll(variables.first.mapNotNull { if (it is XNamedValue) it.name else null })
|
||||
sorted.sort()
|
||||
val varString = sorted.joinToString()
|
||||
out(INDENT_VARIABLES, "($varString)")
|
||||
}
|
||||
|
||||
protected fun out(text: String) {
|
||||
println(text, ProcessOutputTypes.SYSTEM)
|
||||
}
|
||||
|
||||
protected fun out(indent: Int, text: String) {
|
||||
println("\t".repeat(indent) + text, ProcessOutputTypes.SYSTEM)
|
||||
println(text)
|
||||
}
|
||||
|
||||
protected fun Throwable.stackTraceAsString(): String {
|
||||
val writer = StringWriter()
|
||||
printStackTrace(PrintWriter(writer))
|
||||
return writer.toString()
|
||||
}
|
||||
|
||||
fun printStackFrame(files: TestFiles, preferences: DebuggerPreferences) {
|
||||
val asyncStackTraceProvider = getAsyncStackTraceProvider()
|
||||
|
||||
doWhenXSessionPausedThenResume {
|
||||
printContext(debugProcess.debuggerContext)
|
||||
val suspendContext = debuggerSession.xDebugSession?.getSuspendContext()
|
||||
var executionStack = suspendContext?.getActiveExecutionStack()
|
||||
if (executionStack != null) {
|
||||
try {
|
||||
out("Thread stack trace:")
|
||||
val stackFrames: List<XStackFrame> = XDebuggerTestUtil.collectFrames(executionStack)
|
||||
val suspendContextImpl = suspendContext as SuspendContextImpl
|
||||
suspendContextImpl.runActionInSuspendCommand {
|
||||
for (frame in stackFrames) {
|
||||
if (frame is JavaStackFrame) {
|
||||
out(frame)
|
||||
val stackFrames = suspendContext.invokeInSuspendManagerThread(debugProcess) {
|
||||
asyncStackTraceProvider?.getAsyncStackTrace(frame, suspendContextImpl)
|
||||
}
|
||||
if (stackFrames != null) {
|
||||
if (stackFrames is PreflightProvider) {
|
||||
val preflightFrame = stackFrames.getPreflight()
|
||||
out(0, preflightFrame.coroutineInfoData.key.toString())
|
||||
}
|
||||
for (frameItem in stackFrames) {
|
||||
if (frameItem is CreationCoroutineStackFrameItem && frameItem.first)
|
||||
out(0, "Creation stack frame")
|
||||
|
||||
val frame: XStackFrame? = suspendContext.invokeInSuspendManagerThread(debugProcess) {
|
||||
frameItem.createFrame(debugProcess)
|
||||
}
|
||||
frame?.let {
|
||||
out(frame)
|
||||
}
|
||||
|
||||
}
|
||||
return@runActionInSuspendCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
val stackTrace = e.stackTraceAsString()
|
||||
System.err.println("Exception occurred on calculating async stack traces: $stackTrace")
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
println("FrameProxy is 'null', can't calculate async stack trace", ProcessOutputTypes.SYSTEM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getAsyncStackTraceProvider(): CoroutineAsyncStackTraceProvider? {
|
||||
val area = Extensions.getArea(null)
|
||||
if (!area.hasExtensionPoint(ASYNC_STACKTRACE_EP_NAME)) {
|
||||
System.err.println("${ASYNC_STACKTRACE_EP_NAME} extension point is not found (probably old IDE version)")
|
||||
return null
|
||||
}
|
||||
|
||||
val extensionPoint = area.getExtensionPoint<Any>(ASYNC_STACKTRACE_EP_NAME)
|
||||
val provider = extensionPoint.extensions.firstIsInstanceOrNull<CoroutineAsyncStackTraceProvider>()
|
||||
|
||||
if (provider == null) {
|
||||
System.err.println("Kotlin coroutine async stack trace provider is not found")
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
override fun addMavenDependency(compilerFacility: DebuggerTestCompilerFacility, library: String) {
|
||||
val regex = Regex(pattern = """maven\(([a-zA-Z0-9_\-\.]+)\:([a-zA-Z0-9_\-\.]+):([a-zA-Z0-9_\-\.]+)\)(\-javaagent)?""")
|
||||
val result = regex.matchEntire(library) ?: return
|
||||
val (_, groupId: String, artifactId: String, version: String, agent: String) = result.groupValues
|
||||
if ("-javaagent" == agent)
|
||||
agentList.add(JpsMavenRepositoryLibraryDescriptor(groupId, artifactId, version, false))
|
||||
val description = JpsMavenRepositoryLibraryDescriptor(groupId, artifactId, version)
|
||||
val artifacts = loadDependencies(description)
|
||||
compilerFacility.addDependencies(artifacts.map { it.file.presentableUrl })
|
||||
addLibraries(artifacts)
|
||||
}
|
||||
|
||||
override fun createJavaParameters(mainClass: String?): JavaParameters {
|
||||
val params = super.createJavaParameters(mainClass)
|
||||
for (entry in classPath) {
|
||||
params.classPath.add(entry)
|
||||
}
|
||||
for (agent in agentList) {
|
||||
val dependencies = loadDependencies(agent)
|
||||
for (dependency in dependencies) {
|
||||
params.vmParametersList.add("-javaagent:${dependency.file.presentableUrl}")
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
private fun addLibraries(artifacts: MutableList<OrderRoot>) =
|
||||
EdtTestUtil.runInEdtAndWait {
|
||||
runWriteAction {
|
||||
val model = ModuleRootManager.getInstance(myModule).modifiableModel
|
||||
val customLibEditor = NewLibraryEditor().apply {
|
||||
for (artifact in artifacts) {
|
||||
classPath.add(artifact.file.presentableUrl) // for sandbox jvm
|
||||
addRoot(artifact.file, artifact.type)
|
||||
}
|
||||
}
|
||||
ConfigLibraryUtil.addLibrary(customLibEditor, model, null) // for kotlin compiler
|
||||
model.commit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDependencies(
|
||||
description: JpsMavenRepositoryLibraryDescriptor
|
||||
): MutableList<OrderRoot> {
|
||||
return JarRepositoryManager.loadDependenciesSync(
|
||||
project, description, setOf(ArtifactKind.ARTIFACT),
|
||||
RemoteRepositoryDescription.DEFAULT_REPOSITORIES, null
|
||||
) ?: throw AssertionError("Maven Dependency not found: $description")
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.debugger.test
|
||||
import com.intellij.debugger.actions.MethodSmartStepTarget
|
||||
import com.intellij.debugger.engine.*
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.JvmSteppingCommandProvider
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
@@ -157,4 +158,19 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun SuspendContextImpl.runActionInSuspendCommand(action: SuspendContextImpl.() -> Unit) {
|
||||
if (myInProgress) {
|
||||
action()
|
||||
} else {
|
||||
val command = object : SuspendContextCommandImpl(this) {
|
||||
override fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
action(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to execute the action inside a command if we aren't already inside it.
|
||||
debuggerSession.process.managerThread?.invoke(command) ?: command.contextAction(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class XCoroutinesStackTraceTestGenerated extends AbstractXCoroutinesStackTraceTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInXcoroutines() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("coroutineSuspendFun.kt")
|
||||
public void testCoroutineSuspendFun() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("coroutineSuspendFun136.kt")
|
||||
public void testCoroutineSuspendFun136() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun136.kt");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.test.util;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.util.ui.TextTransferable;
|
||||
import com.intellij.xdebugger.frame.XExecutionStack;
|
||||
import com.intellij.xdebugger.frame.XStackFrame;
|
||||
import com.intellij.xdebugger.frame.XValue;
|
||||
import com.intellij.xdebugger.frame.XValueContainer;
|
||||
import com.intellij.xdebugger.impl.frame.XStackFrameContainerEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Kotlin clone of com.intellij.xdebugger.XDebuggerTestUtil
|
||||
*/
|
||||
public class XDebuggerTestUtil {
|
||||
public static final int TIMEOUT_MS = 25_000;
|
||||
|
||||
public static List<XStackFrame> collectFrames(@NotNull XExecutionStack thread) {
|
||||
return collectFrames(thread, TIMEOUT_MS * 2);
|
||||
}
|
||||
|
||||
public static List<XStackFrame> collectFrames(XExecutionStack thread, long timeout) {
|
||||
return collectFrames(thread, timeout, XDebuggerTestUtil::waitFor);
|
||||
}
|
||||
|
||||
public static List<XStackFrame> collectFrames(XExecutionStack thread, long timeout, BiFunction<Semaphore, Long, Boolean> waitFunction) {
|
||||
return collectFramesWithError(thread, timeout, waitFunction).first;
|
||||
}
|
||||
|
||||
public static String getFramePresentation(XStackFrame frame) {
|
||||
TextTransferable.ColoredStringBuilder builder = new TextTransferable.ColoredStringBuilder();
|
||||
frame.customizePresentation(builder);
|
||||
return builder.getBuilder().toString();
|
||||
}
|
||||
|
||||
public static Pair<List<XStackFrame>, String> collectFramesWithError(XExecutionStack thread, long timeout, BiFunction<Semaphore, Long, Boolean> waitFunction) {
|
||||
XTestStackFrameContainer container = new XTestStackFrameContainer();
|
||||
thread.computeStackFrames(0, container);
|
||||
return container.waitFor(timeout, waitFunction);
|
||||
}
|
||||
|
||||
public static boolean waitFor(Semaphore semaphore, long timeoutInMillis) {
|
||||
long end = System.currentTimeMillis() + timeoutInMillis;
|
||||
long remaining = timeoutInMillis;
|
||||
do {
|
||||
try {
|
||||
return semaphore.tryAcquire(remaining, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException ignored) {
|
||||
remaining = end - System.currentTimeMillis();
|
||||
}
|
||||
} while (remaining > 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class XTestStackFrameContainer extends XTestContainer<XStackFrame> implements XStackFrameContainerEx {
|
||||
public volatile XStackFrame frameToSelect;
|
||||
|
||||
@Override
|
||||
public void addStackFrames(@NotNull List<? extends XStackFrame> stackFrames, boolean last) {
|
||||
addChildren(stackFrames, last);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStackFrames(@NotNull List<? extends XStackFrame> stackFrames, @Nullable XStackFrame toSelect, boolean last) {
|
||||
if (toSelect != null) frameToSelect = toSelect;
|
||||
addChildren(stackFrames, last);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void errorOccurred(@NotNull String errorMessage) {
|
||||
setErrorMessage(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<XValue> collectChildren(XValueContainer value) {
|
||||
return collectChildren(value, XDebuggerTestUtil::waitFor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<XValue> collectChildren(XValueContainer value, BiFunction<Semaphore, Long, Boolean> waitFunction) {
|
||||
final Pair<List<XValue>, String> childrenWithError = collectChildrenWithError(value, waitFunction);
|
||||
final String error = childrenWithError.second;
|
||||
assertNull("Error getting children: " + error, error);
|
||||
return childrenWithError.first;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Pair<List<XValue>, String> collectChildrenWithError(XValueContainer value) {
|
||||
return collectChildrenWithError(value, XDebuggerTestUtil::waitFor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Pair<List<XValue>, String> collectChildrenWithError(XValueContainer value,
|
||||
BiFunction<Semaphore, Long, Boolean> waitFunction) {
|
||||
XTestCompositeNode container = new XTestCompositeNode();
|
||||
value.computeChildren(container);
|
||||
|
||||
return container.waitFor(TIMEOUT_MS, waitFunction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.test.util;
|
||||
|
||||
import com.intellij.xdebugger.frame.XCompositeNode;
|
||||
import com.intellij.xdebugger.frame.XValue;
|
||||
import com.intellij.xdebugger.frame.XValueChildrenList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class XTestCompositeNode extends XTestContainer<XValue> implements XCompositeNode {
|
||||
@Override
|
||||
public void addChildren(@NotNull XValueChildrenList children, boolean last) {
|
||||
final List<XValue> list = new ArrayList<>();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
list.add(children.getValue(i));
|
||||
}
|
||||
addChildren(list, last);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlreadySorted(boolean alreadySorted) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.test.util;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.xdebugger.frame.XDebuggerTreeNodeHyperlink;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class XTestContainer<T> {
|
||||
private final List<T> myChildren = new SmartList<>();
|
||||
private String myErrorMessage;
|
||||
private final Semaphore myFinished = new Semaphore(0);
|
||||
|
||||
public void addChildren(List<? extends T> children, boolean last) {
|
||||
myChildren.addAll(children);
|
||||
if (last) myFinished.release();
|
||||
}
|
||||
|
||||
public void tooManyChildren(int remaining) {
|
||||
myFinished.release();
|
||||
}
|
||||
|
||||
public void setMessage(@NotNull String message, Icon icon, @NotNull final SimpleTextAttributes attributes, @Nullable XDebuggerTreeNodeHyperlink link) {
|
||||
}
|
||||
|
||||
public void setErrorMessage(@NotNull String message, @Nullable XDebuggerTreeNodeHyperlink link) {
|
||||
setErrorMessage(message);
|
||||
}
|
||||
|
||||
public void setErrorMessage(@NotNull String errorMessage) {
|
||||
myErrorMessage = errorMessage;
|
||||
myFinished.release();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Pair<List<T>, String> waitFor(long timeoutMs) {
|
||||
return waitFor(timeoutMs, (semaphore, timeout) -> XDebuggerTestUtil.waitFor(myFinished, timeout));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Pair<List<T>, String> waitFor(long timeoutMs, BiFunction<? super Semaphore, ? super Long, Boolean> waitFunction) {
|
||||
if (!waitFunction.apply(myFinished, timeoutMs)) {
|
||||
throw new AssertionError("Waiting timed out");
|
||||
}
|
||||
|
||||
return Pair.create(myChildren, myErrorMessage);
|
||||
}
|
||||
}
|
||||
29
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt
vendored
Normal file
29
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.kt
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package coroutine1
|
||||
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.coroutines.startCoroutine
|
||||
|
||||
fun main() {
|
||||
val cnt = Continuation<Int>(EmptyCoroutineContext) { }
|
||||
val result = ::test1.startCoroutine(1, cnt)
|
||||
println(result)
|
||||
}
|
||||
|
||||
suspend fun test1(i: Int): Int {
|
||||
val test1 = "a"
|
||||
a(test1)
|
||||
return i
|
||||
}
|
||||
|
||||
suspend fun a(aParam: String) {
|
||||
val a = "a"
|
||||
b(a)
|
||||
a + 1
|
||||
}
|
||||
|
||||
suspend fun b(bParam: String) {
|
||||
val b = "b"
|
||||
//Breakpoint!
|
||||
b + 1
|
||||
}
|
||||
26
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.out
vendored
Normal file
26
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendFun.out
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
LineBreakpoint created at suspendFun.kt:28
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
suspendFun.kt:28
|
||||
Thread stack trace:
|
||||
b:28, SuspendFunKt (coroutine1)
|
||||
($completion, b, bParam)
|
||||
a:21, SuspendFunKt (coroutine1)
|
||||
($completion, $continuation, $result, a, aParam)
|
||||
CoroutineNameIdState(name=coroutine, id=-1, state=UNKNOWN, dispatcher=null)
|
||||
test1:15, SuspendFunKt (coroutine1)
|
||||
(i, test1)
|
||||
invoke:9, SuspendFunKt$main$result$1 (coroutine1)
|
||||
(continuation, p1, this)
|
||||
invokeSuspend:121, IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3 (kotlin.coroutines.intrinsics)
|
||||
($i$a$-createCoroutineFromSuspendFunction-IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$2, it, result, this)
|
||||
resumeWith:33, BaseContinuationImpl (kotlin.coroutines.jvm.internal)
|
||||
($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this)
|
||||
startCoroutine:128, ContinuationKt (kotlin.coroutines)
|
||||
($this$startCoroutine, completion, receiver)
|
||||
main:9, SuspendFunKt (coroutine1)
|
||||
(cnt)
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
kotlin.Unit
|
||||
26
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt
vendored
Normal file
26
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.kt
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package continuation
|
||||
|
||||
fun main() {
|
||||
val a = "a"
|
||||
fibonacci().take(10).toList()
|
||||
}
|
||||
|
||||
fun nextSequence(terms: Pair<Int, Int>): Pair<Int, Int> {
|
||||
val terms1 = Pair(terms.second, terms.first + terms.second)
|
||||
if (terms1.first == 8) {
|
||||
//Breakpoint!
|
||||
return terms1
|
||||
} else
|
||||
return terms1
|
||||
}
|
||||
|
||||
fun fibonacci() = sequence {
|
||||
var terms = Pair(0, 1)
|
||||
var step = 0
|
||||
|
||||
while (true) {
|
||||
yield(terms.first)
|
||||
terms = nextSequence(terms)
|
||||
step++
|
||||
}
|
||||
}
|
||||
27
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out
vendored
Normal file
27
idea/jvm-debugger/jvm-debugger-test/testData/continuation/suspendLambda.out
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
LineBreakpoint created at suspendLambda.kt:12
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
suspendLambda.kt:12
|
||||
Thread stack trace:
|
||||
nextSequence:12, SuspendLambdaKt (continuation)
|
||||
(terms, terms1)
|
||||
invokeSuspend:23, SuspendLambdaKt$fibonacci$1 (continuation)
|
||||
($result, $this$sequence, step, terms, this)
|
||||
CoroutineNameIdState(name=coroutine, id=-1, state=UNKNOWN, dispatcher=null)
|
||||
resumeWith:33, kotlin.coroutines.jvm.internal.BaseContinuationImpl
|
||||
($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this)
|
||||
hasNext:140, kotlin.sequences.SequenceBuilderIterator
|
||||
(step, this)
|
||||
hasNext:406, kotlin.sequences.TakeSequence$iterator$1
|
||||
(this)
|
||||
toCollection:722, kotlin.sequences.SequencesKt___SequencesKt
|
||||
($this$toCollection, destination)
|
||||
toMutableList:752, kotlin.sequences.SequencesKt___SequencesKt
|
||||
($this$toMutableList)
|
||||
toList:743, kotlin.sequences.SequencesKt___SequencesKt
|
||||
($this$toList)
|
||||
main:5, continuation.SuspendLambdaKt
|
||||
(a)
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
29
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt
vendored
Normal file
29
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.kt
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package continuation
|
||||
// ATTACH_LIBRARY: maven(org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.4)-javaagent
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.yield
|
||||
|
||||
fun main() {
|
||||
val main = "main"
|
||||
runBlocking {
|
||||
a()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun a() {
|
||||
val a = "a"
|
||||
b(a)
|
||||
val aLate = "a" // to prevent stackFrame to collapse
|
||||
}
|
||||
|
||||
suspend fun b(paramA: String) {
|
||||
yield()
|
||||
val b = "b"
|
||||
c(b)
|
||||
}
|
||||
|
||||
suspend fun c(paramB: String) {
|
||||
val c = "c"
|
||||
//Breakpoint!
|
||||
}
|
||||
46
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out
vendored
Normal file
46
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun.out
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
LineBreakpoint created at coroutineSuspendFun.kt:29
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
coroutineSuspendFun.kt:29
|
||||
Thread stack trace:
|
||||
c:29, CoroutineSuspendFunKt (continuation)
|
||||
($completion, c, paramB)
|
||||
b:23, CoroutineSuspendFunKt (continuation)
|
||||
($completion, $continuation, $result, b, paramA)
|
||||
CoroutineNameIdState(name=coroutine, id=1, state=RUNNING, dispatcher=BlockingEventLoop@6f45df59)
|
||||
a:16, CoroutineSuspendFunKt (continuation)
|
||||
(a)
|
||||
invokeSuspend:10, CoroutineSuspendFunKt$main$1 (continuation)
|
||||
($this$runBlocking)
|
||||
resumeWith:33, BaseContinuationImpl (kotlin.coroutines.jvm.internal)
|
||||
($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this)
|
||||
run:56, DispatchedTask (kotlinx.coroutines)
|
||||
($i$a$-withCoroutineContext-DispatchedTask$run$1, $i$f$withCoroutineContext, context, continuation, countOrElement$iv, delegate, exception, fatalException, job, oldValue$iv, state, taskContext, this)
|
||||
processNextEvent:272, EventLoopImplBase (kotlinx.coroutines)
|
||||
(delayed, this)
|
||||
joinBlocking:79, BlockingCoroutine (kotlinx.coroutines)
|
||||
(this)
|
||||
runBlocking:54, BuildersKt__BuildersKt (kotlinx.coroutines)
|
||||
(block, context, contextInterceptor, coroutine, currentThread, eventLoop, newContext)
|
||||
runBlocking:1, BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
runBlocking$default:36, BuildersKt__BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
runBlocking$default:1, BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
main:9, CoroutineSuspendFunKt (continuation)
|
||||
(main)
|
||||
Creation stack frame
|
||||
createCoroutineUnintercepted:116, IntrinsicsKt__IntrinsicsJvmKt (kotlin.coroutines.intrinsics)
|
||||
()
|
||||
startCoroutineCancellable:26, CancellableKt (kotlinx.coroutines.intrinsics)
|
||||
()
|
||||
runBlocking$default:1, BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
main:9, CoroutineSuspendFunKt (continuation)
|
||||
()
|
||||
main:-1, CoroutineSuspendFunKt (continuation)
|
||||
()
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
29
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun136.kt
vendored
Normal file
29
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun136.kt
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package continuation
|
||||
// ATTACH_LIBRARY: maven(org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5-SNAPSHOT)-javaagent
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.yield
|
||||
|
||||
fun main() {
|
||||
val main = "main"
|
||||
runBlocking {
|
||||
a()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun a() {
|
||||
val a = "a"
|
||||
b(a)
|
||||
val aLate = "a" // to prevent stackFrame to collapse
|
||||
}
|
||||
|
||||
suspend fun b(paramA: String) {
|
||||
yield()
|
||||
val b = "b"
|
||||
c(b)
|
||||
}
|
||||
|
||||
suspend fun c(paramB: String) {
|
||||
val c = "c"
|
||||
//Breakpoint!
|
||||
}
|
||||
46
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun136.out
vendored
Normal file
46
idea/jvm-debugger/jvm-debugger-test/testData/xcoroutines/coroutineSuspendFun136.out
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
LineBreakpoint created at coroutineSuspendFun136.kt:29
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
coroutineSuspendFun136.kt:29
|
||||
Thread stack trace:
|
||||
c:29, CoroutineSuspendFun136Kt (continuation)
|
||||
($completion, c, paramB)
|
||||
b:23, CoroutineSuspendFun136Kt (continuation)
|
||||
($completion, $continuation, $result, b, paramA)
|
||||
CoroutineNameIdState(name=coroutine, id=1, state=RUNNING, dispatcher=BlockingEventLoop@305fd85d)
|
||||
a:16, CoroutineSuspendFun136Kt (continuation)
|
||||
(a)
|
||||
invokeSuspend:10, CoroutineSuspendFun136Kt$main$1 (continuation)
|
||||
($this$runBlocking)
|
||||
resumeWith:33, BaseContinuationImpl (kotlin.coroutines.jvm.internal)
|
||||
($i$a$-with-BaseContinuationImpl$resumeWith$1, $this$with, completion, current, param, result, this)
|
||||
run:56, DispatchedTask (kotlinx.coroutines)
|
||||
($i$a$-withCoroutineContext-DispatchedTask$run$1, $i$f$withCoroutineContext, context, continuation, countOrElement$iv, delegate, exception, fatalException, job, oldValue$iv, state, taskContext, this)
|
||||
processNextEvent:274, EventLoopImplBase (kotlinx.coroutines)
|
||||
(delayed, task, this)
|
||||
joinBlocking:79, BlockingCoroutine (kotlinx.coroutines)
|
||||
(this)
|
||||
runBlocking:54, BuildersKt__BuildersKt (kotlinx.coroutines)
|
||||
(block, context, contextInterceptor, coroutine, currentThread, eventLoop, newContext)
|
||||
runBlocking:1, BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
runBlocking$default:36, BuildersKt__BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
runBlocking$default:1, BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
main:9, CoroutineSuspendFun136Kt (continuation)
|
||||
(main)
|
||||
Creation stack frame
|
||||
createCoroutineUnintercepted:116, IntrinsicsKt__IntrinsicsJvmKt (kotlin.coroutines.intrinsics)
|
||||
()
|
||||
startCoroutineCancellable:26, CancellableKt (kotlinx.coroutines.intrinsics)
|
||||
()
|
||||
runBlocking$default:1, BuildersKt (kotlinx.coroutines)
|
||||
()
|
||||
main:9, CoroutineSuspendFun136Kt (continuation)
|
||||
()
|
||||
main:-1, CoroutineSuspendFun136Kt (continuation)
|
||||
()
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -128,6 +128,9 @@ sealed class BaseExecutionContext(val evaluationContext: EvaluationContextImpl)
|
||||
fun findClassSafe(className: String): ClassType? =
|
||||
hopelessAware { findClass(className) as? ClassType }
|
||||
|
||||
fun invokeMethodSafe(type: ClassType, method: Method, args: List<Value?>): Value? {
|
||||
return hopelessAware { debugProcess.invokeMethod(evaluationContext, type, method, args) }
|
||||
}
|
||||
|
||||
fun invokeMethodAsString(instance: ObjectReference, methodName: String): String? =
|
||||
(findAndInvoke(instance, instance.referenceType(), methodName, "()Ljava/lang/String;") as? StringReference)?.value() ?: null
|
||||
|
||||
@@ -118,7 +118,7 @@ private inline fun <T> wrapEvaluateException(block: () -> T): T? {
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> wrapIllegalArgumentException(block: () -> T): T? {
|
||||
public inline fun <T> wrapIllegalArgumentException(block: () -> T): T? {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
@@ -61,3 +61,6 @@ org.jetbrains.kotlin.idea.debugger.test.AsyncStackTraceTestGenerated.testAsyncLa
|
||||
org.jetbrains.kotlin.idea.debugger.test.AsyncStackTraceTestGenerated.testAsyncSimple, redesign test AsyncStackTraces
|
||||
org.jetbrains.kotlin.idea.debugger.test.KotlinSteppingTestGenerated.StepOver.testStepOverInlinedLambdaStdlib, fails after advancing bootstrap KT-37879
|
||||
org.jetbrains.kotlin.incremental.IncrementalJsKlibCompilerRunnerTestGenerated.ClassHierarchyAffected.testMethodRemoved, FO in klib required
|
||||
org.jetbrains.kotlin.idea.debugger.test.XCoroutinesStackTraceTestGenerated.testCoroutineSuspendFun135, running test on SNAPSHOT version
|
||||
org.jetbrains.kotlin.idea.debugger.test.XCoroutinesStackTraceTestGenerated.testCoroutineSuspendFun, unstable
|
||||
org.jetbrains.kotlin.idea.debugger.test.ContinuationStackTraceTestGenerated.testSuspendLambda, unstable
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 12 and column 132.
|
Reference in New Issue
Block a user