coroutine debugger toolbar added

This commit is contained in:
Vladimir Ilmov
2020-01-08 15:48:08 +01:00
committed by Vladimir Ilmov
parent e570450c59
commit cac4a991f5
16 changed files with 342 additions and 91 deletions
@@ -19,12 +19,12 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<CoroutineAsyncStackFrameItem>? { override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<CoroutineAsyncStackFrameItem>? {
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList() val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList()
return null return stackFrameList
} }
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem>? { fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem>? {
val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList() val stackFrameList = hopelessAware { getAsyncStackTraceSafe(stackFrame.stackFrameProxy, suspendContext) } ?: emptyList()
return null return stackFrameList
} }
fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem> { fun getAsyncStackTraceSafe(frameProxy: StackFrameProxyImpl, suspendContext: XSuspendContext): List<CoroutineAsyncStackFrameItem> {
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.debugger.coroutine
import com.intellij.ui.components.JBLabel
import com.intellij.ui.SimpleListCellRenderer
import javax.swing.ListCellRenderer
class VersionedImplementationProvider() {
fun comboboxListCellRenderer(): ListCellRenderer<in String>? =
SimpleListCellRenderer.create<String> { label: JBLabel, value: String?, index: Int ->
if (value != null) {
label.text = value
} else if (index >= 0) {
label.text = "Loading..."
}
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.debugger.coroutine
import com.intellij.ui.components.JBLabel
import org.jetbrains.kotlin.idea.debugger.coroutine.override.com.intellij.ui.SimpleListCellRenderer
import javax.swing.ListCellRenderer
class VersionedImplementationProvider() {
fun comboboxListCellRenderer(): ListCellRenderer<in String>? =
SimpleListCellRenderer.create<String> { label: JBLabel, value: String?, index: Int ->
if (value != null) {
label.text = value
} else if (index >= 0) {
label.text = "Loading..."
}
}
}
@@ -71,14 +71,15 @@ class CoroutineBuilder(val suspendContext: XSuspendContext) {
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, xStackFrame)) coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy, xStackFrame))
} }
} }
} else if (coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) { } else if ((coroutine.state == CoroutineInfoData.State.SUSPENDED || coroutine.activeThread == null) && coroutine.lastObservedFrameFieldRef is ObjectReference) {
// to get frames from CoroutineInfo anyway // to get frames from CoroutineInfo anyway
// the thread is paused on breakpoint - it has at least one frame // the thread is paused on breakpoint - it has at least one frame
val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex + 1) val suspendedStackTrace = coroutine.stackTrace.take(creationFrameSeparatorIndex + 1)
for (suspendedFrame in suspendedStackTrace) { for (suspendedFrame in suspendedStackTrace) {
val suspendedXStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, suspendedFrame) val suspendedXStackFrame = stackFrame(positionManager, firstSuspendedStackFrameProxyImpl, suspendedFrame)
coroutineStackFrameList.add( coroutineStackFrameList.add(
SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame) SuspendCoroutineStackFrameItem(firstSuspendedStackFrameProxyImpl, suspendedFrame, suspendedXStackFrame, coroutine.lastObservedFrameFieldRef)
) )
} }
} }
@@ -167,10 +168,6 @@ class CoroutineBuilder(val suspendContext: XSuspendContext) {
private fun isResumeMethodFrame(frame: StackFrameProxyImpl) = private fun isResumeMethodFrame(frame: StackFrameProxyImpl) =
frame.safeLocation()?.safeMethod()?.name() == "resumeWith" frame.safeLocation()?.safeMethod()?.name() == "resumeWith"
/**
* Should be invoked on manager thread
* This code was migrated from previous implementation, has to be refactored @TODO
*/
private fun createSyntheticStackFrame( private fun createSyntheticStackFrame(
descriptor: SuspendStackFrameDescriptor, descriptor: SuspendStackFrameDescriptor,
pos: XSourcePosition, pos: XSourcePosition,
@@ -206,6 +203,7 @@ class CreationCoroutineStackFrameItem(
val location: Location val location: Location
) : CoroutineStackFrameItem(frame, stackFrame) { ) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() = location override fun location() = location
fun emptyDescriptor() = fun emptyDescriptor() =
EmptyStackFrameDescriptor(stackTraceElement, frame) EmptyStackFrameDescriptor(stackTraceElement, frame)
} }
@@ -213,9 +211,13 @@ class CreationCoroutineStackFrameItem(
class SuspendCoroutineStackFrameItem( class SuspendCoroutineStackFrameItem(
frame: StackFrameProxyImpl, frame: StackFrameProxyImpl,
val stackTraceElement: StackTraceElement, val stackTraceElement: StackTraceElement,
stackFrame: XStackFrame stackFrame: XStackFrame,
val lastObservedFrameFieldRef: ObjectReference
) : CoroutineStackFrameItem(frame, stackFrame) { ) : CoroutineStackFrameItem(frame, stackFrame) {
override fun location() = frame.location() override fun location() = frame.location()
fun emptyDescriptor() =
EmptyStackFrameDescriptor(stackTraceElement, frame)
} }
class AsyncCoroutineStackFrameItem( class AsyncCoroutineStackFrameItem(
@@ -19,7 +19,7 @@ data class CoroutineInfoData(
val stackTrace: List<StackTraceElement>, val stackTrace: List<StackTraceElement>,
// links to jdi.* references // links to jdi.* references
val activeThread: ThreadReference? = null, // for suspended coroutines should be null val activeThread: ThreadReference? = null, // for suspended coroutines should be null
val frame: ObjectReference? val lastObservedFrameFieldRef: ObjectReference?
) { ) {
var stackFrameList = mutableListOf<CoroutineStackFrameItem>() var stackFrameList = mutableListOf<CoroutineStackFrameItem>()
@@ -35,7 +35,7 @@ class CoroutineStackTraceData(
val lookupContinuation = LookupContinuation(context, frame) val lookupContinuation = LookupContinuation(context, frame)
// retrieve continuation only if suspend method // retrieve continuation only if suspend method
val continuation = lookupContinuation.findContinuation(infoData) val continuation = lookupContinuation.findContinuation(infoData.lastObservedFrameFieldRef)
return if (continuation is ObjectReference) return if (continuation is ObjectReference)
SuspendStackFrameDescriptor(infoData, frame, proxy, continuation) SuspendStackFrameDescriptor(infoData, frame, proxy, continuation)
@@ -0,0 +1,116 @@
/*
* 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.override.com.intellij.ui;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.Function;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import sun.swing.DefaultLookup;
import javax.swing.*;
import javax.swing.plaf.basic.BasicHTML;
import java.awt.*;
/**
* JBLabel-based (text and icon) list cell renderer.
*
* @see ColoredListCellRenderer for more complex SimpleColoredComponent-based variant.
*
* @author gregsh
*/
public abstract class SimpleListCellRenderer<T> extends JBLabel implements ListCellRenderer<T> {
@NotNull
public static <T> SimpleListCellRenderer<T> create(@NotNull String nullValue, @NotNull Function<? super T, String> getText) {
return new SimpleListCellRenderer<T>() {
@Override
public void customize(JList<? extends T> list, T value, int index, boolean selected, boolean hasFocus) {
setText(value == null ? nullValue : getText.fun(value));
}
};
}
@NotNull
public static <T> SimpleListCellRenderer<T> create(@NotNull Customizer<? super T> customizer) {
return new SimpleListCellRenderer<T>() {
@Override
public void customize(JList<? extends T> list, T value, int index, boolean selected, boolean hasFocus) {
customizer.customize(this, value, index);
}
};
}
@Override
public Component getListCellRendererComponent(JList<? extends T> list, T value, int index, boolean isSelected, boolean cellHasFocus) {
setComponentOrientation(list.getComponentOrientation());
setBorder(JBUI.Borders.empty(1));
Color bg, fg;
JList.DropLocation dropLocation = list.getDropLocation();
if (dropLocation != null && !dropLocation.isInsert() && dropLocation.getIndex() == index) {
bg = DefaultLookup.getColor(this, ui, "List.dropCellBackground");
fg = DefaultLookup.getColor(this, ui, "List.dropCellForeground");
isSelected = true;
}
else {
bg = isSelected ? list.getSelectionBackground() : list.getBackground();
fg = isSelected ? list.getSelectionForeground() : list.getForeground();
}
setBackground(bg);
setForeground(fg);
setFont(list.getFont());
setText("");
setIcon(null);
customize(list, value, index, isSelected, cellHasFocus);
setOpaque(isSelected);
return this;
}
public abstract void customize(JList<? extends T> list, T value, int index, boolean selected, boolean hasFocus);
@Override
public Dimension getPreferredSize() {
if (StringUtil.isNotEmpty(getText())) {
return super.getPreferredSize();
}
setText(" ");
Dimension size = super.getPreferredSize();
setText("");
return size;
}
@FunctionalInterface
public interface Customizer<T> {
void customize(@NotNull JBLabel label, T value, int index);
}
// @formatter:off
@Override public void validate() {}
@Override public void invalidate() {}
@Override public void repaint() {}
@Override public void revalidate() {}
@Override public void repaint(long tm, int x, int y, int width, int height) {}
@Override public void repaint(Rectangle r) {}
@Override public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {}
@Override public void firePropertyChange(String propertyName, char oldValue, char newValue) {}
@Override public void firePropertyChange(String propertyName, short oldValue, short newValue) {}
@Override public void firePropertyChange(String propertyName, int oldValue, int newValue) {}
@Override public void firePropertyChange(String propertyName, long oldValue, long newValue) {}
@Override public void firePropertyChange(String propertyName, float oldValue, float newValue) {}
@Override public void firePropertyChange(String propertyName, double oldValue, double newValue) {}
@Override public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {}
@Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
if (propertyName == "text"
|| ((propertyName == "font" || propertyName == "foreground")
&& oldValue != newValue
&& getClientProperty(BasicHTML.propertyKey) != null)) {
super.firePropertyChange(propertyName, oldValue, newValue);
}
}
// @formatter:on
}
@@ -36,7 +36,11 @@ class AsyncStackTraceContext(
fun getAsyncStackTraceIfAny() : List<CoroutineAsyncStackFrameItem> { fun getAsyncStackTraceIfAny() : List<CoroutineAsyncStackFrameItem> {
val continuation = locateContinuation() ?: return emptyList() val continuation = locateContinuation() ?: return emptyList()
val frames = mutableListOf<CoroutineAsyncStackFrameItem>() val frames = mutableListOf<CoroutineAsyncStackFrameItem>()
collectFramesRecursively(continuation, frames) try {
collectFramesRecursively(continuation, frames)
} catch (e: Exception) {
log.error("Error while looking for variables.", e)
}
return frames return frames
} }
@@ -71,9 +75,9 @@ class AsyncStackTraceContext(
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
val location = createLocation(continuation) val location = createLocation(continuation)
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
location?.let { location?.let {
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
consumer.add(CoroutineAsyncStackFrameItem(location, spilledVariables)) consumer.add(CoroutineAsyncStackFrameItem(location, spilledVariables))
} }
@@ -86,7 +90,9 @@ class AsyncStackTraceContext(
val instance = invokeGetStackTraceElement(continuation) ?: return null val instance = invokeGetStackTraceElement(continuation) ?: return null
val className = context.invokeMethodAsString(instance, "getClassName") ?: return null val className = context.invokeMethodAsString(instance, "getClassName") ?: return null
val methodName = context.invokeMethodAsString(instance, "getMethodName") ?: return null val methodName = context.invokeMethodAsString(instance, "getMethodName") ?: return null
val lineNumber = context.invokeMethodAsInt(instance,"getLineNumber") ?: return null val lineNumber = context.invokeMethodAsInt(instance,"getLineNumber")?.takeIf {
it >= 0
} ?: return null
val locationClass = context.findClassSafe(className) ?: return null val locationClass = context.findClassSafe(className) ?: return null
log.warn("Got location of ${className}.${methodName}:${lineNumber} in ${locationClass}") log.warn("Got location of ${className}.${methodName}:${lineNumber} in ${locationClass}")
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber) return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
@@ -35,11 +35,11 @@ class LookupContinuation(val context: ExecutionContext, val frame: StackTraceEle
* Gets current CoroutineInfo.lastObservedFrame and finds next frames in it until null or needed stackTraceElement is found * 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 * @return null if matching continuation is not found or is not BaseContinuationImpl
*/ */
fun findContinuation(infoData: CoroutineInfoData): ObjectReference? { fun findContinuation(initialContinuation: ObjectReference?): ObjectReference? {
if (!isApplicable()) if (!isApplicable())
return null return null
var continuation = infoData.frame ?: return null var continuation = initialContinuation ?: return null
val baseType = "kotlin.coroutines.jvm.internal.BaseContinuationImpl" val baseType = "kotlin.coroutines.jvm.internal.BaseContinuationImpl"
val getTrace = (continuation.type() as ClassType).concreteMethodByName( val getTrace = (continuation.type() as ClassType).concreteMethodByName(
"getStackTraceElement", "getStackTraceElement",
@@ -81,4 +81,15 @@ class LookupContinuation(val context: ExecutionContext, val frame: StackTraceEle
val next = type.concreteMethodByName("getCompletion", "()Lkotlin/coroutines/Continuation;") val next = type.concreteMethodByName("getCompletion", "()Lkotlin/coroutines/Continuation;")
return context.invokeMethod(continuation, next, emptyList()) as? ObjectReference return context.invokeMethod(continuation, next, emptyList()) as? ObjectReference
} }
fun findGetStackTraceElementMethodRef(continuation: ObjectReference): Method =
(continuation.type() as ClassType).concreteMethodByName("getStackTraceElement", "()Ljava/lang/StackTraceElement;")
fun createAsyncStackTraceContext(stackTraceElementMethodRef: Method) =
AsyncStackTraceContext(context, stackTraceElementMethodRef)
fun createAsyncStackTraceContext(continuation: ObjectReference): AsyncStackTraceContext? {
val getStackTraceElementMethodRef = findGetStackTraceElementMethodRef(continuation)
return createAsyncStackTraceContext(getStackTraceElementMethodRef)
}
} }
@@ -13,7 +13,7 @@ public class Utils {
Object instance, Object instance,
String methodName, String methodName,
Object... args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { Object... args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
@SuppressWarnings("rawtypes")
Class[] classes = new Class[args.length]; Class[] classes = new Class[args.length];
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if(args[i] instanceof ReflectionSpecificType) if(args[i] instanceof ReflectionSpecificType)
@@ -23,10 +23,10 @@ public class Utils {
} }
Method method = instance.getClass().getDeclaredMethod(methodName, classes); Method method = instance.getClass().getDeclaredMethod(methodName, classes);
method.setAccessible(true); method.setAccessible(true);
Object r = method.invoke(instance, args); return method.invoke(instance, args);
return r;
} }
@SuppressWarnings("rawtypes")
public interface ReflectionSpecificType { public interface ReflectionSpecificType {
Class specificClass(); Class specificClass();
} }
@@ -235,8 +235,8 @@ class CoroutineDumpPanel(project: Project, consoleView: ConsoleView, toolbarActi
} }
private inner class MergeStackTracesAction : ToggleAction( private inner class MergeStackTracesAction : ToggleAction(
"Merge Identical Stacktraces", "Merge Identical Stacktrace",
"Group coroutines with identical stacktraces", "Group coroutines with identical stacktrace",
AllIcons.Actions.Collapseall AllIcons.Actions.Collapseall
), DumbAware { ), DumbAware {
@@ -8,18 +8,25 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.view
import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.settings.ThreadsViewSettings import com.intellij.debugger.settings.ThreadsViewSettings
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.ide.highlighter.JavaHighlightingColors
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
import com.intellij.openapi.editor.HighlighterColors import com.intellij.openapi.editor.HighlighterColors
import com.intellij.openapi.editor.colors.CodeInsightColors import com.intellij.openapi.editor.colors.CodeInsightColors
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.ui.ColoredTextContainer import com.intellij.ui.ColoredTextContainer
import com.intellij.ui.SimpleColoredComponent import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.SimpleTextAttributes
import com.intellij.xdebugger.frame.presentation.XValuePresentation import com.intellij.xdebugger.frame.presentation.XValuePresentation
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
import com.intellij.xdebugger.ui.DebuggerColors
import com.sun.jdi.Location import com.sun.jdi.Location
import com.sun.jdi.ReferenceType import com.sun.jdi.ReferenceType
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import java.awt.Color
import javax.swing.Icon import javax.swing.Icon
class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) { class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
@@ -45,15 +52,19 @@ class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
for (i in 0 until size) { for (i in 0 until size) {
val text: String = texts.get(i) val text: String = texts.get(i)
val attribute: TextAttributesKey = textKeyAttributes.get(i) val attribute: TextAttributesKey = textKeyAttributes.get(i)
val simpleTextAttrinbute = toSimpleTextAttribute(attribute)
component.append(text, when(attribute) { component.append(text, simpleTextAttrinbute)
CoroutineDebuggerColors.REGULAR_ATTRIBUTES -> SimpleTextAttributes.REGULAR_ATTRIBUTES
CoroutineDebuggerColors.VALUE_ATTRIBUTES -> XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
})
} }
} }
private fun toSimpleTextAttribute(attribute: TextAttributesKey) =
when (attribute) {
CoroutineDebuggerColors.REGULAR_ATTRIBUTES -> SimpleTextAttributes.REGULAR_ATTRIBUTES
CoroutineDebuggerColors.VALUE_ATTRIBUTES -> XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES
else -> SimpleTextAttributes.REGULAR_ATTRIBUTES
}
fun forEachTextBlock(f: (Pair<String, TextAttributesKey>) -> Unit) { fun forEachTextBlock(f: (Pair<String, TextAttributesKey>) -> Unit) {
for (pair in texts zip textKeyAttributes) for (pair in texts zip textKeyAttributes)
f(pair) f(pair)
@@ -83,14 +94,13 @@ class SimpleColoredTextIcon(val icon: Icon?, val hasChildrens: Boolean) {
interface CoroutineDebuggerColors { interface CoroutineDebuggerColors {
companion object { companion object {
val REGULAR_ATTRIBUTES = val REGULAR_ATTRIBUTES = HighlighterColors.TEXT
TextAttributesKey.createTextAttributesKey("REGULAR_ATTRIBUTES", HighlighterColors.NO_HIGHLIGHTING) val VALUE_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("KOTLIN_COROUTINE_DEBUGGER_VALUE", HighlighterColors.TEXT)
val VALUE_ATTRIBUTES =
TextAttributesKey.createTextAttributesKey("VALUE_ATTRIBUTES", CodeInsightColors.WARNINGS_ATTRIBUTES)
} }
} }
class SimpleColoredTextIconPresentationRenderer { class SimpleColoredTextIconPresentationRenderer {
val log by logger
private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance() private val settings: ThreadsViewSettings = ThreadsViewSettings.getInstance()
fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon { fun render(infoData: CoroutineInfoData): SimpleColoredTextIcon {
@@ -157,7 +167,10 @@ class SimpleColoredTextIconPresentationRenderer {
} }
if (settings.SHOW_SOURCE_NAME) { if (settings.SHOW_SOURCE_NAME) {
label.append(", ") label.append(", ")
val sourceName = DebuggerUtilsEx.getSourceName(location) { e: Throwable? -> "Unknown Source" } val sourceName = DebuggerUtilsEx.getSourceName(location) { e: Throwable? ->
log.error("Error while trying to resolve sourceName for location", e, location.toString())
"Unknown Source"
}
label.append(sourceName) label.append(sourceName)
} }
return label return label
@@ -5,23 +5,35 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.view package org.jetbrains.kotlin.idea.debugger.coroutine.view
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaDebugProcess import com.intellij.debugger.engine.JavaDebugProcess
import com.intellij.debugger.engine.JavaExecutionStack import com.intellij.debugger.engine.JavaExecutionStack
import com.intellij.debugger.engine.SuspendContextImpl import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.ide.CommonActionsManager
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.ui.CaptionPanel
import com.intellij.ui.ComboboxSpeedSearch
import com.intellij.ui.DoubleClickListener import com.intellij.ui.DoubleClickListener
import com.intellij.ui.OnePixelSplitter import com.intellij.ui.border.CustomLineBorder
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.panels.Wrapper
import com.intellij.util.SingleAlarm import com.intellij.util.SingleAlarm
import com.intellij.xdebugger.XDebugSession import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebuggerUtil import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.XSourcePosition import com.intellij.xdebugger.XSourcePosition
import com.intellij.xdebugger.frame.* import com.intellij.xdebugger.frame.*
import com.intellij.xdebugger.impl.actions.XDebuggerActions
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel
@@ -29,31 +41,34 @@ import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeRestorer
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
import com.sun.jdi.ClassType
import javaslang.control.Either import javaslang.control.Either
import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineAsyncStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutineDebuggerContentInfo.Companion.XCOROUTINE_POPUP_ACTION_GROUP
import org.jetbrains.kotlin.idea.debugger.coroutine.VersionedImplementationProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.command.* import org.jetbrains.kotlin.idea.debugger.coroutine.command.*
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.data.SuspendStackFrameDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutine.data.SyntheticStackFrame import org.jetbrains.kotlin.idea.debugger.coroutine.data.SyntheticStackFrame
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ApplicationThreadExecutor import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.*
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.AsyncStackTraceContext import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParams
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy import org.jetbrains.kotlin.idea.debugger.coroutine.util.CreateContentParamsProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.ManagerThreadExecutor import org.jetbrains.kotlin.idea.debugger.coroutine.util.XDebugSessionListenerProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.util.* import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import java.awt.BorderLayout
import java.awt.event.KeyAdapter import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
import javax.swing.JPanel
class XCoroutineView(val project: Project, val session: XDebugSession) : class XCoroutineView(val project: Project, val session: XDebugSession) :
Disposable, XDebugSessionListenerProvider, CreateContentParamsProvider { Disposable, XDebugSessionListenerProvider, CreateContentParamsProvider {
val log by logger val log by logger
val splitter = OnePixelSplitter("SomeKey", 0.25f) val versionedImplementationProvider = VersionedImplementationProvider()
val mainPanel = JPanel(BorderLayout())
val someCombobox = ComboBox<String>()
val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null) val panel = XDebuggerTreePanel(project, session.debugProcess.editorsProvider, this, null, XCOROUTINE_POPUP_ACTION_GROUP, null)
val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this) val alarm = SingleAlarm(Runnable { resetRoot() }, VIEW_CLEAR_DELAY, this)
val javaDebugProcess = session.debugProcess as JavaDebugProcess val javaDebugProcess = session.debugProcess as JavaDebugProcess
@@ -70,11 +85,36 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
} }
init { init {
splitter.firstComponent = panel.mainPanel someCombobox.setRenderer(versionedImplementationProvider.comboboxListCellRenderer())
object : ComboboxSpeedSearch(someCombobox) {
override fun getElementText(element: Any?): String? {
return element.toString()
}
}
someCombobox.addItem(null)
val myToolbar = createToolbar()
val myThreadsPanel = Wrapper()
myThreadsPanel.setBorder(CustomLineBorder(CaptionPanel.CNT_ACTIVE_BORDER_COLOR, 0, 0, 1, 0))
myThreadsPanel.add(myToolbar?.getComponent(), BorderLayout.EAST)
myThreadsPanel.add(someCombobox, BorderLayout.CENTER)
mainPanel.add(myThreadsPanel, BorderLayout.NORTH)
mainPanel.add(panel.mainPanel, BorderLayout.CENTER)
selectedNodeListener.installOn() selectedNodeListener.installOn()
} }
private fun createToolbar(): ActionToolbarImpl? {
val framesGroup = DefaultActionGroup()
val actionsManager = CommonActionsManager.getInstance()
framesGroup
.addAll(ActionManager.getInstance().getAction(XDebuggerActions.FRAMES_TOP_TOOLBAR_GROUP))
val toolbar = ActionManager.getInstance().createActionToolbar(
ActionPlaces.DEBUGGER_TOOLBAR, framesGroup, true
) as ActionToolbarImpl
toolbar.setReservePlaceAutoPopupIcon(false)
return toolbar
}
fun saveState() { fun saveState() {
DebuggerUIUtil.invokeLater { DebuggerUIUtil.invokeLater {
if (! (panel.tree.root is EmptyNode)) { if (! (panel.tree.root is EmptyNode)) {
@@ -113,7 +153,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
override fun createContentParams(): CreateContentParams = override fun createContentParams(): CreateContentParams =
CreateContentParams( CreateContentParams(
CoroutineDebuggerContentInfo.XCOROUTINE_THREADS_CONTENT, CoroutineDebuggerContentInfo.XCOROUTINE_THREADS_CONTENT,
splitter, mainPanel,
KotlinBundle.message("debugger.session.tab.xcoroutine.title"), KotlinBundle.message("debugger.session.tab.xcoroutine.title"),
null, null,
panel.tree panel.tree
@@ -148,7 +188,7 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
} }
node.addChildren(children, true) node.addChildren(children, true)
} else { } else {
node.addChildren(XValueChildrenList.singleton(ErrorNode("Error occured while fetching information")), true) node.addChildren(XValueChildrenList.singleton(ErrorNode("Error occurs while fetching information")), true)
} }
} }
} }
@@ -208,24 +248,33 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
applyRenderer(node, presentation) applyRenderer(node, presentation)
} }
data class KeyMouseEvent(val keyEvent: KeyEvent?, val mouseEvent: MouseEvent?) {
constructor(keyEvent: KeyEvent) : this(keyEvent, null)
constructor(mouseEvent: MouseEvent) : this(null, mouseEvent)
fun isKeyEvent() = keyEvent != null
fun isMouseEvent() = mouseEvent != null
}
inner class XDebuggerTreeSelectedNodeListener(val tree: XDebuggerTree) { inner class XDebuggerTreeSelectedNodeListener(val tree: XDebuggerTree) {
fun installOn() { fun installOn() {
object : DoubleClickListener() { object : DoubleClickListener() {
override fun onDoubleClick(e: MouseEvent) = override fun onDoubleClick(e: MouseEvent) =
nodeSelected(Either.left(e)) nodeSelected(KeyMouseEvent(e))
}.installOn(tree) }.installOn(tree)
tree.addKeyListener(object : KeyAdapter() { tree.addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) { override fun keyPressed(e: KeyEvent) {
val key = e.keyCode val key = e.keyCode
if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT) if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT)
nodeSelected(Either.right(e)) nodeSelected(KeyMouseEvent(e))
} }
}) })
} }
fun nodeSelected(event: Either<MouseEvent, KeyEvent>) : Boolean { fun nodeSelected(event: KeyMouseEvent) : Boolean {
val selectedNodes = tree.getSelectedNodes(XValueNodeImpl::class.java, null) val selectedNodes = tree.getSelectedNodes(XValueNodeImpl::class.java, null)
if (selectedNodes.size == 1) { if (selectedNodes.size == 1) {
val node = selectedNodes[0] val node = selectedNodes[0]
@@ -242,21 +291,17 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
is CreationCoroutineStackFrameItem -> { is CreationCoroutineStackFrameItem -> {
val position = getPosition(frame.stackTraceElement) ?: return false val position = getPosition(frame.stackTraceElement) ?: return false
val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
val stackFrame = createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) })
createStackAndSetFrame(threadProxy, { SyntheticStackFrame(frame.emptyDescriptor(), emptyList(), position) })
} }
is SuspendCoroutineStackFrameItem -> { is SuspendCoroutineStackFrameItem -> {
val position = getPosition(frame.stackTraceElement) ?: return false val threadProxy = threadSuspendContext.thread as ThreadReferenceProxyImpl
val executionContext = executionContext(threadSuspendContext, frame.frame)
createStackAndSetFrame(threadProxy, { createSyntheticStackFrame(executionContext, frame) })
} }
is AsyncCoroutineStackFrameItem -> { is AsyncCoroutineStackFrameItem -> {
} }
// else -> { else -> {
// val (stack, stackFrame) = createSyntheticStackFrame(descriptor, pos) ?: return }
// val action: () -> Unit = { context.debuggerSession?.xDebugSession?.setCurrentStackFrame(stack, stackFrame) }
// - ApplicationManager.getApplication()
// - .invokeLater(action, ModalityState.stateForComponent(this@CoroutinesDebuggerTree))
// }
} }
} }
} }
@@ -264,21 +309,22 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
} }
} }
fun createSyntheticStackFrame() {} fun createStackAndSetFrame(threadReferenceProxy: ThreadReferenceProxyImpl, stackFrameProvider: () -> XStackFrame?, isCurrentContext: Boolean = false) {
fun createStackAndSetFrame(threadReferenceProxy: ThreadReferenceProxyImpl, stackFrameProvider: () -> XStackFrame, isCurrentContext: Boolean = false) {
val threadSuspendContext = session.suspendContext as SuspendContextImpl val threadSuspendContext = session.suspendContext as SuspendContextImpl
managerThreadExecutor.on(threadSuspendContext).schedule { managerThreadExecutor.on(threadSuspendContext).schedule {
val stackFrame = stackFrameProvider.invoke() val stackFrame = stackFrameProvider.invoke()
val executionStack = createExecutionStack(threadReferenceProxy, isCurrentContext) if(stackFrame is XStackFrame) {
applicationThreadExecutor.schedule( val executionStack = createExecutionStack(threadReferenceProxy, isCurrentContext)
{ applicationThreadExecutor.schedule(
session.setCurrentStackFrame(executionStack, stackFrame) {
}, panel.tree) session.setCurrentStackFrame(executionStack, stackFrame)
}, panel.tree
)
}
} }
} }
fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false) : XExecutionStack { private fun createExecutionStack(proxy: ThreadReferenceProxyImpl, isCurrentContext: Boolean = false) : XExecutionStack {
val executionStack = CoroutineDebuggerExecutionStack(proxy, isCurrentContext) val executionStack = CoroutineDebuggerExecutionStack(proxy, isCurrentContext)
executionStack.initTopFrame() executionStack.initTopFrame()
return executionStack return executionStack
@@ -300,30 +346,22 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber) return XDebuggerUtil.getInstance().createPosition(classFile, lineNumber)
} }
private fun executionContext(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl) : ExecutionContext {
val evaluationContextImpl = EvaluationContextImpl(suspendContext, frameProxy)
return ExecutionContext(evaluationContextImpl, frameProxy)
}
private fun createSyntheticStackFrame( private fun createSyntheticStackFrame(
descriptor: SuspendStackFrameDescriptor, executionContext: ExecutionContext,
pos: XSourcePosition frame: SuspendCoroutineStackFrameItem
): Pair<XExecutionStack, SyntheticStackFrame>? { ): SyntheticStackFrame? {
val context = DebuggerManagerEx.getInstanceEx(project).context val position = getPosition(frame.stackTraceElement) ?: return null
val suspendContext = context.suspendContext ?: return null val lookupContinuation = LookupContinuation(executionContext, frame.stackTraceElement)
val proxy = suspendContext.thread ?: return null val continuation = lookupContinuation.findContinuation(frame.lastObservedFrameFieldRef) ?: return null
val executionStack = JavaExecutionStack(proxy, suspendContext.debugProcess, false)
executionStack.initTopFrame() val asyncStackTraceContext = lookupContinuation.createAsyncStackTraceContext(continuation)
val evalContext = context.createEvaluationContext() val vars = asyncStackTraceContext?.getSpilledVariables(continuation) ?: return null
val frameProxy = evalContext?.frameProxy ?: return null return SyntheticStackFrame(frame.emptyDescriptor(), vars, position)
val execContext = ExecutionContext(evalContext, frameProxy)
val continuation = descriptor.continuation // guaranteed that it is a BaseContinuationImpl
val aMethod = (continuation.type() as ClassType).concreteMethodByName(
"getStackTraceElement",
"()Ljava/lang/StackTraceElement;"
)
val vars = with(CoroutineAsyncStackTraceProvider()) {
AsyncStackTraceContext(
execContext,
aMethod
).getSpilledVariables(continuation)
} ?: return null
return executionStack to SyntheticStackFrame(descriptor, vars, pos)
} }
} }
@@ -10,12 +10,14 @@ import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.memory.utils.StackFrameItem import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.execution.process.ProcessOutputTypes import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.extensions.Extensions 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.CoroutineAsyncStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.getSafe import org.jetbrains.kotlin.utils.getSafe
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.lang.reflect.Field
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithStepping() { abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithStepping() {
@@ -81,9 +83,10 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
appendln("Async stack trace:") appendln("Async stack trace:")
for (item in trace) { for (item in trace) {
append(MARGIN).appendln(item.toString()) append(MARGIN).appendln(item.toString())
val declaredFields = listDeclaredFields(item.javaClass)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val variablesField = item.javaClass.declaredFields val variablesField = declaredFields
.first { !Modifier.isStatic(it.modifiers) && it.type == List::class.java } .first { !Modifier.isStatic(it.modifiers) && it.type == List::class.java }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -100,4 +103,14 @@ abstract class AbstractAsyncStackTraceTest : KotlinDescriptorTestCaseWithSteppin
} }
} }
} }
private fun listDeclaredFields(cls: Class<in Any>): MutableList<Field> {
var clazz = cls
val declaredFields = mutableListOf<Field>()
while (clazz != Class.forName("java.lang.Object")) {
declaredFields.addAll(clazz.declaredFields)
clazz = clazz.superclass
}
return declaredFields
}
} }
@@ -86,4 +86,9 @@
<option name="FOREGROUND" value="20999d" /> <option name="FOREGROUND" value="20999d" />
</value> </value>
</option> </option>
<option name="KOTLIN_COROUTINE_DEBUGGER_VALUE">
<value>
<option name="FOREGROUND" value="FF8C8B" />
</value>
</option>
</list> </list>
@@ -76,4 +76,9 @@
<option name="ERROR_STRIPE_COLOR" value="ff0000"/> <option name="ERROR_STRIPE_COLOR" value="ff0000"/>
</value> </value>
</option> </option>
<option name="KOTLIN_COROUTINE_DEBUGGER_VALUE">
<value>
<option name="FOREGROUND" value="840000" />
</value>
</option>
</list> </list>