Debugger: Fix for coroutine debugger after review
This commit is contained in:
committed by
Vladimir Ilmov
parent
5975251a32
commit
12313fa5aa
+5
-3
@@ -25,7 +25,7 @@ import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.text.DateFormatUtil
|
||||
import com.intellij.xdebugger.impl.XDebuggerManagerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.createExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
@Suppress("ComponentNotRegistered")
|
||||
class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
|
||||
@@ -39,14 +39,16 @@ class CoroutineDumpAction : AnAction(), AnAction.TransparentUpdate {
|
||||
val process = context.debugProcess ?: return
|
||||
process.managerThread.schedule(object : SuspendContextCommandImpl(context.suspendContext) {
|
||||
override fun contextAction() {
|
||||
val execContext = context.createExecutionContext() ?: return
|
||||
val evalContext = context.createEvaluationContext()
|
||||
val frameProxy = evalContext?.frameProxy ?: return
|
||||
val execContext = ExecutionContext(evalContext, frameProxy)
|
||||
val states = CoroutinesDebugProbesProxy.dumpCoroutines(execContext)
|
||||
if (states.isLeft) {
|
||||
logger.warn(states.left)
|
||||
XDebuggerManagerImpl.NOTIFICATION_GROUP
|
||||
.createNotification(
|
||||
"Coroutine dump failed. See log",
|
||||
MessageType.ERROR
|
||||
MessageType.WARNING
|
||||
).notify(project)
|
||||
return
|
||||
}
|
||||
|
||||
+21
-36
@@ -80,7 +80,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
}
|
||||
}
|
||||
|
||||
exporterToTextFile = createToFileExporter(project, dump)
|
||||
exporterToTextFile = MyToFileExporter(project, dump)
|
||||
|
||||
val filterAction = FilterAction().apply {
|
||||
registerCustomShortcutSet(
|
||||
@@ -92,7 +92,7 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
add(filterAction)
|
||||
add(CopyToClipboardAction(dump, project))
|
||||
add(ActionManager.getInstance().getAction(IdeActions.ACTION_EXPORT_TO_TEXT_FILE))
|
||||
add(MergeStacktracesAction())
|
||||
add(MergeStackTracesAction())
|
||||
}
|
||||
add(
|
||||
ActionManager.getInstance()
|
||||
@@ -139,7 +139,6 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
val states = if (UISettings.instance.state.mergeEqualStackTraces) mergedDump else dump
|
||||
for (state in states) {
|
||||
if (StringUtil.containsIgnoreCase(state.stringStackTrace, text) || StringUtil.containsIgnoreCase(state.name, text)) {
|
||||
|
||||
model.addElement(state)
|
||||
if (selection === state) {
|
||||
selectedIndex = index
|
||||
@@ -211,9 +210,11 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FilterAction :
|
||||
ToggleAction("Filter", "Show only threads containing a specific string", AllIcons.General.Filter),
|
||||
DumbAware {
|
||||
private inner class FilterAction : ToggleAction(
|
||||
"Filter",
|
||||
"Show only coroutines containing a specific string",
|
||||
AllIcons.General.Filter
|
||||
), DumbAware {
|
||||
|
||||
override fun isSelected(e: AnActionEvent): Boolean {
|
||||
return filterPanel.isVisible
|
||||
@@ -229,14 +230,11 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private inner class MergeStacktracesAction :
|
||||
ToggleAction(
|
||||
"Merge Identical Stacktraces",
|
||||
"Group coroutines with identical stacktraces",
|
||||
AllIcons.General.CollapseAll
|
||||
),
|
||||
DumbAware {
|
||||
private inner class MergeStackTracesAction : ToggleAction(
|
||||
"Merge Identical Stacktraces",
|
||||
"Group coroutines with identical stacktraces",
|
||||
AllIcons.Actions.Collapseall
|
||||
), DumbAware {
|
||||
|
||||
override fun isSelected(e: AnActionEvent): Boolean {
|
||||
return UISettings.instance.state.mergeEqualStackTraces
|
||||
@@ -268,32 +266,19 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
|
||||
private val group = NotificationGroup.toolWindowGroup("Analyze coroutine dump", ToolWindowId.RUN, false)
|
||||
}
|
||||
|
||||
private fun createToFileExporter(project: Project, states: List<CoroutineState>): ExporterToTextFile {
|
||||
return MyToFileExporter(project, states)
|
||||
}
|
||||
private class MyToFileExporter(
|
||||
private val myProject: Project,
|
||||
private val states: List<CoroutineState>
|
||||
) : ExporterToTextFile {
|
||||
|
||||
private class MyToFileExporter(private val myProject: Project, private val states: List<CoroutineState>) :
|
||||
ExporterToTextFile {
|
||||
|
||||
override fun getReportText(): String {
|
||||
val sb = StringBuilder()
|
||||
for (state in states) {
|
||||
sb.append(state.stringStackTrace).append("\n\n")
|
||||
}
|
||||
return sb.toString()
|
||||
override fun getReportText() = buildString {
|
||||
for (state in states)
|
||||
append(state.stringStackTrace).append("\n\n")
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun getDefaultFilePath(): String {
|
||||
val baseDir = myProject.baseDir
|
||||
return if (baseDir != null) {
|
||||
baseDir.presentableUrl + File.separator + defaultReportFileName
|
||||
} else ""
|
||||
}
|
||||
override fun getDefaultFilePath() = (myProject.basePath ?: "") + File.separator + defaultReportFileName
|
||||
|
||||
override fun canExport(): Boolean {
|
||||
return states.isNotEmpty()
|
||||
}
|
||||
override fun canExport() = states.isNotEmpty()
|
||||
|
||||
private val defaultReportFileName = "coroutines_report.txt"
|
||||
}
|
||||
|
||||
+2
-2
@@ -61,13 +61,13 @@ class CoroutineState(
|
||||
val trace = context.invokeMethod(continuation, getTrace, emptyList()) as? ObjectReference
|
||||
if (trace != null)
|
||||
(context.invokeMethod(trace, getClassName, emptyList()) as StringReference).value()
|
||||
else ""
|
||||
else null
|
||||
}
|
||||
val lineNumber = {
|
||||
val trace = context.invokeMethod(continuation, getTrace, emptyList()) as? ObjectReference
|
||||
if (trace != null)
|
||||
(context.invokeMethod(trace, getLineNumber, emptyList()) as IntegerValue).value()
|
||||
else -239 // invalid line number (but well-educated)
|
||||
else null
|
||||
}
|
||||
|
||||
while (continuation.type().isSubtype(baseType)
|
||||
|
||||
+5
-4
@@ -40,7 +40,7 @@ class CoroutinesDebugConfigurationExtension : RunConfigurationExtension() {
|
||||
private var Project.listenerCreated: Boolean? by UserDataProperty(Key.create("COROUTINES_DEBUG_TAB_CREATE_LISTENER"))
|
||||
|
||||
override fun isApplicableFor(configuration: RunConfigurationBase<*>): Boolean {
|
||||
return Registry.`is`("kotlin.debugger" + ".coroutines")
|
||||
return Registry.`is`("kotlin.debugger.coroutines")
|
||||
}
|
||||
|
||||
override fun <T : RunConfigurationBase<*>?> updateJavaParameters(
|
||||
@@ -48,7 +48,7 @@ class CoroutinesDebugConfigurationExtension : RunConfigurationExtension() {
|
||||
params: JavaParameters?,
|
||||
runnerSettings: RunnerSettings?
|
||||
) {
|
||||
if (!Registry.`is`("kotlin.debugger" + ".coroutines")) return
|
||||
if (!Registry.`is`("kotlin.debugger.coroutines")) return
|
||||
if (runnerSettings is DebuggingRunnerData && params != null
|
||||
&& params.classPath != null
|
||||
&& params.classPath.pathList.isNotEmpty()
|
||||
@@ -78,8 +78,9 @@ class CoroutinesDebugConfigurationExtension : RunConfigurationExtension() {
|
||||
override fun processStarted(debugProcess: XDebugProcess) {
|
||||
DebuggerInvocationUtil.swingInvokeLater(project) {
|
||||
val session = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession
|
||||
if (session != null)
|
||||
registerCoroutinesPanel(session.xDebugSession?.ui ?: return@swingInvokeLater, session)
|
||||
val ui = session?.xDebugSession?.ui
|
||||
if (ui != null)
|
||||
registerCoroutinesPanel(ui, session)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-9
@@ -16,6 +16,7 @@ object CoroutinesDebugProbesProxy {
|
||||
private const val DEBUG_PACKAGE = "kotlinx.coroutines.debug"
|
||||
private var DebugProcess.references by UserDataProperty(Key.create<ProcessReferences>("COROUTINES_DEBUG_REFERENCES"))
|
||||
|
||||
@Synchronized
|
||||
@Suppress("unused")
|
||||
fun install(context: ExecutionContext) {
|
||||
val debugProbes = context.findClass("$DEBUG_PACKAGE.DebugProbes") as ClassType
|
||||
@@ -24,6 +25,7 @@ object CoroutinesDebugProbesProxy {
|
||||
context.invokeMethod(instance, install, emptyList())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Suppress("unused")
|
||||
fun uninstall(context: ExecutionContext) {
|
||||
val debugProbes = context.findClass("$DEBUG_PACKAGE.DebugProbes") as ClassType
|
||||
@@ -36,6 +38,7 @@ object CoroutinesDebugProbesProxy {
|
||||
* Invokes DebugProbes from debugged process's classpath and returns states of coroutines
|
||||
* Should be invoked on debugger manager thread
|
||||
*/
|
||||
@Synchronized
|
||||
fun dumpCoroutines(context: ExecutionContext): Either<Throwable, List<CoroutineState>> {
|
||||
try {
|
||||
if (context.debugProcess.references == null) {
|
||||
@@ -50,6 +53,7 @@ object CoroutinesDebugProbesProxy {
|
||||
|
||||
return Either.right(List(size) {
|
||||
val index = context.vm.mirrorOf(it)
|
||||
// `List<CoroutineInfo>.get(index)`
|
||||
val elem = context.invokeMethod(infoList, refs.getElement, listOf(index)) as ObjectReference
|
||||
val name = getName(context, elem, refs)
|
||||
val state = getState(context, elem, refs)
|
||||
@@ -73,7 +77,11 @@ object CoroutinesDebugProbesProxy {
|
||||
refs: ProcessReferences
|
||||
): String {
|
||||
// equals to `coroutineInfo.context.get(CoroutineName).name`
|
||||
val coroutineContextInst = context.invokeMethod(info, refs.getContext, emptyList()) as ObjectReference
|
||||
val coroutineContextInst = context.invokeMethod(
|
||||
info,
|
||||
refs.getContext,
|
||||
emptyList()
|
||||
) as? ObjectReference ?: throw IllegalArgumentException("Coroutine context must not be null")
|
||||
val coroutineName = context.invokeMethod(
|
||||
coroutineContextInst,
|
||||
refs.getContextElement, listOf(refs.nameKey)
|
||||
@@ -81,7 +89,8 @@ object CoroutinesDebugProbesProxy {
|
||||
// If the coroutine doesn't have a given name, CoroutineContext.get(CoroutineName) returns null
|
||||
val name = if (coroutineName != null) (context.invokeMethod(
|
||||
coroutineName,
|
||||
refs.getName, emptyList()
|
||||
refs.getName,
|
||||
emptyList()
|
||||
) as StringReferenceImpl).value() else "coroutine"
|
||||
val id = (info.getValue(refs.idField) as LongValue).value()
|
||||
return "$name#$id"
|
||||
@@ -92,7 +101,7 @@ object CoroutinesDebugProbesProxy {
|
||||
info: ObjectReference, // CoroutineInfo instance
|
||||
refs: ProcessReferences
|
||||
): String {
|
||||
// equals to stringState = coroutineInfo.state.toString()
|
||||
// equals to `stringState = coroutineInfo.state.toString()`
|
||||
val state = context.invokeMethod(info, refs.getState, emptyList()) as ObjectReference
|
||||
return (context.invokeMethod(state, refs.toString, emptyList()) as StringReferenceImpl).value()
|
||||
}
|
||||
@@ -100,7 +109,7 @@ object CoroutinesDebugProbesProxy {
|
||||
private fun getLastObservedThread(
|
||||
info: ObjectReference, // CoroutineInfo instance
|
||||
threadRef: Field // reference to lastObservedThread
|
||||
): ThreadReference? = info.getValue(threadRef) as ThreadReference?
|
||||
): ThreadReference? = info.getValue(threadRef) as? ThreadReference
|
||||
|
||||
/**
|
||||
* Returns list of stackTraceElements for the given CoroutineInfo's [ObjectReference]
|
||||
@@ -124,14 +133,12 @@ object CoroutinesDebugProbesProxy {
|
||||
listOf(context.vm.virtualMachine.mirrorOf(it))
|
||||
) as ObjectReference
|
||||
val clazz = (frame.getValue(refs.className) as StringReference).value()
|
||||
|
||||
// if (clazz.contains("$DEBUG_PACKAGE.DebugProbes")) break // cut off debug intrinsic stacktrace
|
||||
list.add(
|
||||
0, // add in the beginning
|
||||
StackTraceElement(
|
||||
clazz,
|
||||
(frame.getValue(refs.methodName) as StringReference).value(),
|
||||
(frame.getValue(refs.fileName) as StringReference?)?.value(),
|
||||
(frame.getValue(refs.methodName) as? StringReference)?.value(),
|
||||
(frame.getValue(refs.fileName) as? StringReference)?.value(),
|
||||
(frame.getValue(refs.line) as IntegerValue).value()
|
||||
)
|
||||
)
|
||||
@@ -165,7 +172,7 @@ object CoroutinesDebugProbesProxy {
|
||||
val getName: Method = coroutineName.methodsByName("getName").single()
|
||||
val nameKey = coroutineName.getValue(coroutineName.fieldByName("Key")) as ObjectReference
|
||||
val toString: Method = (context.findClass("java.lang.Object") as ClassType)
|
||||
.methodsByName("toString").single()
|
||||
.concreteMethodByName("toString", "()Ljava/lang/String;")
|
||||
|
||||
val threadRef: Field = info.fieldByName("lastObservedThread")
|
||||
val continuation: Field = info.fieldByName("lastObservedFrame")
|
||||
|
||||
+16
-12
@@ -41,7 +41,6 @@ import com.sun.jdi.ClassType
|
||||
import javaslang.control.Either
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinCoroutinesAsyncStackTraceProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.createExecutionContext
|
||||
import java.awt.event.MouseEvent
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.swing.event.TreeModelEvent
|
||||
@@ -52,7 +51,7 @@ import javax.swing.event.TreeModelListener
|
||||
*/
|
||||
class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
private val logger = Logger.getInstance(this::class.java)
|
||||
private var lastSuspendContextDump: Pair<WeakReference<SuspendContextImpl>, Either<Throwable, List<CoroutineState>>>? = null
|
||||
private var lastSuspendContextCache: Cache? = null
|
||||
|
||||
override fun createNodeManager(project: Project): NodeManagerImpl {
|
||||
return object : NodeManagerImpl(project, this) {
|
||||
@@ -227,11 +226,13 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
pos: XSourcePosition
|
||||
): Pair<XExecutionStack, SyntheticStackFrame>? {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val proxy = context.suspendContext?.thread ?: return null
|
||||
val executionStack =
|
||||
JavaExecutionStack(proxy, context.debugProcess!!, false)
|
||||
val suspendContext = context.suspendContext ?: return null
|
||||
val proxy = suspendContext.thread ?: return null
|
||||
val executionStack = JavaExecutionStack(proxy, suspendContext.debugProcess, false)
|
||||
executionStack.initTopFrame()
|
||||
val execContext = context.createExecutionContext() ?: return null
|
||||
val evalContext = context.createEvaluationContext()
|
||||
val frameProxy = evalContext?.frameProxy ?: return null
|
||||
val execContext = ExecutionContext(evalContext, frameProxy)
|
||||
val continuation = descriptor.continuation // guaranteed that it is a BaseContinuationImpl
|
||||
val aMethod = (continuation.type() as ClassType).concreteMethodByName(
|
||||
"getStackTraceElement",
|
||||
@@ -255,6 +256,7 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
descriptor: NodeDescriptorImpl,
|
||||
evalContext: EvaluationContextImpl
|
||||
) {
|
||||
val creationStackTraceSeparator = "\b\b\b" // the "\b\b\b" is used for creation stacktrace separator in kotlinx.coroutines
|
||||
if (descriptor !is CoroutineDescriptorImpl) {
|
||||
if (descriptor is CreationFramesDescriptor) {
|
||||
val threadProxy = debuggerContext.suspendContext?.thread ?: return
|
||||
@@ -298,7 +300,7 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
val proxy = threadProxy.forceFrames().first()
|
||||
// the thread is paused on breakpoint - it has at least one frame
|
||||
for (it in descriptor.state.stackTrace) {
|
||||
if (it.className.startsWith("\b\b\b")) break
|
||||
if (it.className.startsWith(creationStackTraceSeparator)) break
|
||||
children.add(createCoroutineFrameDescriptor(descriptor, evalContext, it, proxy))
|
||||
}
|
||||
}
|
||||
@@ -306,7 +308,7 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
}
|
||||
}
|
||||
val trace = descriptor.state.stackTrace
|
||||
val index = trace.indexOfFirst { it.className.startsWith("\b\b\b") }
|
||||
val index = trace.indexOfFirst { it.className.startsWith(creationStackTraceSeparator) }
|
||||
children.add(myNodeManager.createNode(CreationFramesDescriptor(trace.subList(index + 1, trace.size)), evalContext))
|
||||
}
|
||||
|
||||
@@ -371,7 +373,7 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
|
||||
override fun isExpandable(node: DebuggerTreeNodeImpl): Boolean {
|
||||
val descriptor = node.descriptor
|
||||
return if (descriptor is StackFrameDescriptor) return false else descriptor.isExpandable
|
||||
return if (descriptor is StackFrameDescriptor) false else descriptor.isExpandable
|
||||
}
|
||||
|
||||
override fun build(context: DebuggerContextImpl) {
|
||||
@@ -402,11 +404,11 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
}
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext, suspendContext.frameProxy)
|
||||
val executionContext = ExecutionContext(evaluationContext, suspendContext.frameProxy ?: return)
|
||||
val cache = lastSuspendContextDump
|
||||
val cache = lastSuspendContextCache
|
||||
val states = if (cache != null && cache.first.get() === suspendContext) {
|
||||
cache.second
|
||||
} else CoroutinesDebugProbesProxy.dumpCoroutines(executionContext).apply {
|
||||
lastSuspendContextDump = WeakReference(suspendContext) to this
|
||||
lastSuspendContextCache = WeakReference(suspendContext) to this
|
||||
}
|
||||
// if suspend context hasn't changed - use last dump, else compute new
|
||||
if (states.isLeft) {
|
||||
@@ -441,4 +443,6 @@ class CoroutinesDebuggerTree(project: Project) : DebuggerTree(project) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private typealias Cache = Pair<WeakReference<SuspendContextImpl>, Either<Throwable, List<CoroutineState>>>
|
||||
|
||||
+1
@@ -120,6 +120,7 @@ class CoroutinesPanel(project: Project, stateManager: DebuggerStateManager) : De
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
// copied from com.intellij.debugger.ui.impl.ThreadsPanel.updateNodeLabels
|
||||
private fun updateNodeLabels(from: DebuggerTreeNodeImpl) {
|
||||
val children = from.children()
|
||||
try {
|
||||
|
||||
+23
-34
@@ -25,6 +25,7 @@ import com.intellij.openapi.util.IconLoader
|
||||
import com.sun.jdi.ClassType
|
||||
import com.sun.jdi.ObjectReference
|
||||
import org.jetbrains.kotlin.idea.IconExtensionChooser
|
||||
import javaslang.control.Either
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import javax.swing.Icon
|
||||
|
||||
@@ -37,29 +38,16 @@ class CoroutineData(private val state: CoroutineState) : DescriptorData<Coroutin
|
||||
return CoroutineDescriptorImpl(state)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other !is CoroutineData) {
|
||||
false
|
||||
} else state.name == other.state.name
|
||||
}
|
||||
override fun equals(other: Any?) = if (other !is CoroutineData) false else state.name == other.state.name
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return state.name.hashCode()
|
||||
}
|
||||
override fun hashCode() = state.name.hashCode()
|
||||
|
||||
override fun getDisplayKey(): DisplayKey<CoroutineDescriptorImpl> {
|
||||
return SimpleDisplayKey(state.name)
|
||||
}
|
||||
override fun getDisplayKey(): DisplayKey<CoroutineDescriptorImpl> = SimpleDisplayKey(state.name)
|
||||
}
|
||||
|
||||
class CoroutineDescriptorImpl(val state: CoroutineState) : NodeDescriptorImpl() {
|
||||
var suspendContext: SuspendContextImpl? = null
|
||||
val icon: Icon
|
||||
get() = when {
|
||||
state.isSuspended -> AllIcons.Debugger.ThreadSuspended
|
||||
state.state == CoroutineState.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
|
||||
else -> AllIcons.Debugger.ThreadRunning
|
||||
}
|
||||
lateinit var icon: Icon
|
||||
|
||||
override fun getName(): String? {
|
||||
return state.name
|
||||
@@ -76,30 +64,35 @@ class CoroutineDescriptorImpl(val state: CoroutineState) : NodeDescriptorImpl()
|
||||
return state.state != CoroutineState.State.CREATED
|
||||
}
|
||||
|
||||
override fun setContext(context: EvaluationContextImpl?) {
|
||||
private fun calcIcon() = when {
|
||||
state.isSuspended -> AllIcons.Debugger.ThreadSuspended
|
||||
state.state == CoroutineState.State.CREATED -> AllIcons.Debugger.ThreadStates.Idle
|
||||
else -> AllIcons.Debugger.ThreadRunning
|
||||
}
|
||||
|
||||
override fun setContext(context: EvaluationContextImpl?) {
|
||||
icon = calcIcon()
|
||||
}
|
||||
}
|
||||
|
||||
class CoroutineStackFrameData private constructor(val state: CoroutineState, private val proxy: StackFrameProxyImpl) :
|
||||
DescriptorData<NodeDescriptorImpl>() {
|
||||
private var frame: StackTraceElement? = null
|
||||
private var frameItem: StackFrameItem? = null
|
||||
private lateinit var frame: Either<StackTraceElement, StackFrameItem>
|
||||
|
||||
constructor(state: CoroutineState, frame: StackTraceElement, proxy: StackFrameProxyImpl) : this(state, proxy) {
|
||||
this.frame = frame
|
||||
this.frame = Either.left(frame)
|
||||
}
|
||||
|
||||
constructor(state: CoroutineState, frameItem: StackFrameItem, proxy: StackFrameProxyImpl) : this(state, proxy) {
|
||||
this.frameItem = frameItem
|
||||
this.frame = Either.right(frameItem)
|
||||
}
|
||||
|
||||
override fun hashCode() = frame?.hashCode() ?: frameItem.hashCode()
|
||||
override fun hashCode(): Int {
|
||||
return if (frame.isLeft) frame.left.hashCode() else frame.get().hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other is CoroutineStackFrameData) {
|
||||
other.frame == frame && other.frameItem == frameItem
|
||||
} else false
|
||||
return other is CoroutineStackFrameData && frame == other.frame
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,12 +100,10 @@ class CoroutineStackFrameData private constructor(val state: CoroutineState, pri
|
||||
* or [AsyncStackFrameDescriptor] according to current frame
|
||||
*/
|
||||
override fun createDescriptorImpl(project: Project): NodeDescriptorImpl {
|
||||
val frame = frame ?: return AsyncStackFrameDescriptor(
|
||||
state,
|
||||
frameItem!!,
|
||||
proxy
|
||||
)
|
||||
val isLeft = frame.isLeft
|
||||
if (!isLeft) return AsyncStackFrameDescriptor(state, frame.get(), proxy)
|
||||
// check whether last fun is suspend fun
|
||||
val frame = frame.left
|
||||
val suspendContext =
|
||||
DebuggerManagerEx.getInstanceEx(project).context.suspendContext ?: return EmptyStackFrameDescriptor(
|
||||
frame,
|
||||
@@ -212,9 +203,7 @@ class EmptyStackFrameDescriptor(val frame: StackTraceElement, proxy: StackFrameP
|
||||
}
|
||||
|
||||
class CreationFramesDescriptor(val frames: List<StackTraceElement>) : NodeDescriptorImpl() {
|
||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?): String {
|
||||
return "Coroutine creation stack trace"
|
||||
}
|
||||
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener?) = name
|
||||
|
||||
override fun setContext(context: EvaluationContextImpl?) {}
|
||||
override fun getName() = "Coroutine creation stack trace"
|
||||
|
||||
-5
@@ -100,9 +100,4 @@ class ExecutionContext(val evaluationContext: EvaluationContextImpl, val framePr
|
||||
@Suppress("DEPRECATION")
|
||||
DebuggerUtilsEx.keep(reference, evaluationContext)
|
||||
}
|
||||
}
|
||||
|
||||
fun DebuggerContextImpl.createExecutionContext(): ExecutionContext? {
|
||||
val context = createEvaluationContext()
|
||||
return ExecutionContext(context ?: return null, frameProxy ?: return null)
|
||||
}
|
||||
Reference in New Issue
Block a user