(CoroutineDebugger) Stack printing logic extracted to the single place

for DebugMetadata and CoroutinePanel, some corner cases fixed, test
cases added.
Added:
 - external maven dependencies in test scenarios
 - comparing stack traces + variables on breakpoint
This commit is contained in:
Vladimir Ilmov
2020-04-14 14:08:59 +02:00
parent 153056b4fd
commit 3cd0eb0fca
47 changed files with 1487 additions and 734 deletions
@@ -15,7 +15,6 @@ 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…
@@ -8,21 +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.data.CoroutineInfoData
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
}
@@ -31,45 +29,21 @@ class CoroutineAsyncStackTraceProvider : AsyncStackTraceProvider {
else stackFrameList
}
private fun lookupForAfterPreflight(
stackFrame: CoroutinePreflightStackFrame,
private fun processPreflight(
preflightFrame: CoroutinePreflightStackFrame,
suspendContext: SuspendContextImpl
): List<CoroutineStackFrameItem>? {
val resumeWithFrame = stackFrame.threadPreCoroutineFrames.firstOrNull()
val resumeWithFrame = preflightFrame.threadPreCoroutineFrames.firstOrNull()
if (threadAndContextSupportsEvaluation(suspendContext, resumeWithFrame)) {
val stackFrames = mutableListOf<CoroutineStackFrameItem>()
stackFrames.addAll(stackFrame.coroutineInfoData.stackTrace)
// val lastRestoredFrame = stackFrame.coroutineInfoData.stackTrace.first()
stackFrames.addAll(stackFrame.threadPreCoroutineFrames.mapIndexed { index, stackFrameProxyImpl ->
// if (index == 0)
// PreCoroutineStackFrameItem(stackFrameProxyImpl, lastRestoredFrame) // get location and variables also from restored part
// else
PreCoroutineStackFrameItem(stackFrameProxyImpl)
})
stackFrames.addAll(stackFrame.coroutineInfoData.creationStackTrace)
return stackFrames
if (threadAndContextSupportsEvaluation(
suspendContext,
resumeWithFrame
)
) {
val doubleFrameList = CoroutineFrameBuilder.build(preflightFrame, suspendContext)
return doubleFrameList.stackTrace + doubleFrameList.creationStackTrace
}
return null
}
fun lookupForResumeContinuation(
frameProxy: StackFrameProxyImpl,
suspendContext: SuspendContextImpl,
framesLeft: List<StackFrameProxyImpl>
): CoroutinePreflightStackFrame? {
val location = frameProxy.location()
if (!location.isInKotlinSources())
return null
if (threadAndContextSupportsEvaluation(suspendContext, frameProxy))
return ContinuationHolder.lookupContinuation(suspendContext, frameProxy, framesLeft)
return null
}
private fun threadAndContextSupportsEvaluation(suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl?) =
suspendContext.supportsEvaluation() && frameProxy?.threadProxy()?.supportsEvaluation() ?: false
}
@@ -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,6 +9,8 @@ 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
@@ -16,14 +18,26 @@ 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 &&
frame !is SkipCoroutineStackFrameProxyImpl &&
AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)
)
ContinuationHolder.coroutineExitFrame(frame, debugProcess.debuggerContext.suspendContext)
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
}
}
@@ -100,5 +100,3 @@ class DebuggerConnection(
return ui.createContent(param.id, param.component, param.displayName, param.icon, param.parentComponent)
}
}
fun coroutineDebuggerTraceEnabled() = Registry.`is`("kotlin.debugger.coroutines.trace")
@@ -1,61 +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.SuspendContextImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
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.ContinuationHolder.Companion.leftThreadStack
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutinePreflightStackFrame
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
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()
for (runningStackFrameProxy in realFrames) {
if (runningStackFrameProxy.location().isPreFlight()) {
val leftThreadStack = leftThreadStack(runningStackFrameProxy) ?: continue
val coroutineStack =
coroutineStackFrameProvider.lookupForResumeContinuation(runningStackFrameProxy, suspendContext, leftThreadStack) ?: continue
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(coroutineStack.stackFrameProxy))
// clue coroutine stack into the thread's real stack
val stackFrameItems = coroutineStack.coroutineInfoData.stackTrace.map {
RestoredCoroutineStackFrameItem(
runningStackFrameProxy,
it.location,
it.spilledVariables
)
}
coroutineStackFrameList.addAll(stackFrameItems)
} else
coroutineStackFrameList.add(RunningCoroutineStackFrameItem(runningStackFrameProxy))
}
} else if (coroutine.isSuspended())
coroutineStackFrameList.addAll(coroutine.stackTrace)
coroutineStackFrameList.addAll(coroutine.creationStackTrace)
return coroutineStackFrameList
}
}
@@ -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?) =
@@ -7,77 +7,57 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
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 com.sun.jdi.StackFrame
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.coroutine.KotlinDebuggerCoroutinesBundle
import org.jetbrains.kotlin.idea.debugger.coroutine.coroutineDebuggerTraceEnabled
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
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,
val first: Boolean
) : CoroutineStackFrameItem(location, emptyList()) {
fun descriptor(frame: StackFrameProxyImpl) =
RestoredStackFrameDescriptor(stackTraceElement, frame)
class FirstCreationCoroutineStackFrameItem(
stackTraceElement: StackTraceElement,
location: Location
) : CreationCoroutineStackFrameItem(stackTraceElement, location) {
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
return CreationCoroutineStackFrame(debugProcess, this)
return if (first)
CreationCoroutineStackFrame(debugProcess, this)
else
super.createFrame(debugProcess)
}
}
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 CreationCoroutineStackFrameItem(
val stackTraceElement: StackTraceElement,
location: Location
) : CoroutineStackFrameItem(location, emptyList()) {
fun emptyDescriptor(frame: StackFrameProxyImpl) =
EmptyStackFrameDescriptor(stackTraceElement, frame)
}
/**
* 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())
}
@@ -87,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())
}
@@ -100,10 +80,10 @@ 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()) :
class PreCoroutineStackFrameItem internal constructor(val frame: StackFrameProxyImpl, location: Location, variables: List<XNamedValue> = emptyList()) :
CoroutineStackFrameItem(location, variables) {
constructor(frame: StackFrameProxyImpl, variables: List<XNamedValue> = emptyList()) : this(frame, frame.location(), variables)
@@ -118,49 +98,34 @@ class PreCoroutineStackFrameItem(val frame: StackFrameProxyImpl, location: Locat
}
}
/**
* Can act as a joint frame, take variables from 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 {
val skipCoroutineFrame = SkipCoroutineStackFrameProxyImpl(frame)
debugProcess.getPositionManager().createStackFrame(skipCoroutineFrame, debugProcess, frame.location())
?.computeChildren(node) // hack but works
class RunningCoroutineStackFrameItem(
val frame: StackFrameProxyImpl,
location: Location,
spilledVariables: List<XNamedValue> = emptyList()
) : CoroutineStackFrameItem(location, spilledVariables) {
override fun createFrame(debugProcess: DebugProcessImpl): CapturedStackFrame {
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())
@@ -7,11 +7,8 @@ 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.LocationCache
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.isAbstractCoroutine
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
/**
* Represents state of a coroutine.
@@ -19,10 +16,10 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
*/
data class CoroutineInfoData(
val key: CoroutineNameIdState,
val stackTrace: MutableList<CoroutineStackFrameItem>,
val stackTrace: List<CoroutineStackFrameItem>,
val creationStackTrace: List<CreationCoroutineStackFrameItem>,
val activeThread: ThreadReference? = null, // for suspended coroutines should be null
val lastObservedFrameFieldRef: ObjectReference? = null
val lastObservedFrame: ObjectReference? = null
) {
fun isSuspended() = key.state == State.SUSPENDED
@@ -32,66 +29,22 @@ 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 {
val log by logger
const val DEFAULT_COROUTINE_NAME = "coroutine"
const val DEFAULT_COROUTINE_STATE = "UNKNOWN"
fun lookup(
input: ObjectReference?,
context: DefaultExecutionContext,
stackFrameItems: List<CoroutineStackFrameItem>
): CoroutineInfoData? {
val locationCache = LocationCache(context)
val creationStackTrace = mutableListOf<CreationCoroutineStackFrameItem>()
val realState = if (input?.type()?.isAbstractCoroutine() ?: false) {
state(input, context) ?: return null
} else {
val ci = DebugProbesImpl(context).getCoroutineInfo(input, context)
if (ci != null) {
if (ci.creationStackTrace != null)
for (frame in ci.creationStackTrace.mapNotNull { it.stackTraceElement() }) {
creationStackTrace.add(CreationCoroutineStackFrameItem(frame, locationCache.createLocation(frame)))
}
CoroutineNameIdState.instance(ci)
} else {
log.warn("Coroutine information not found, ${input?.type()} is not subtype of AbstractCoroutine as expected.")
CoroutineNameIdState(DEFAULT_COROUTINE_NAME, "-1", State.UNKNOWN, null)
}
}
return CoroutineInfoData(realState, stackFrameItems.toMutableList(), creationStackTrace)
}
fun state(value: ObjectReference?, context: DefaultExecutionContext): 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 ?: DEFAULT_COROUTINE_NAME
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(name, id?.toString() ?: hexAddress, state, standAloneCoroutineMirror.context.dispatcher)
}
}
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
}
}
}
@@ -24,7 +24,7 @@ import javax.swing.Icon
class CoroutineDescriptorImpl(val infoData: CoroutineInfoData) : NodeDescriptorImpl() {
lateinit var icon: Icon
override fun getName() = infoData.key?.name
override fun getName() = infoData.key.name
@Throws(EvaluateException::class)
override fun calcRepresentation(context: EvaluationContextImpl?, labelListener: DescriptorLabelListener): String {
@@ -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.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.find { it.name == v.name() } == null }
}
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 == SuspendExitMode.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
}
/**
* Acts as a joint frame, take variables from restored frame and information from the real 'exit' frame.
*/
class PreCoroutineStackFrame(val frame: StackFrameProxyImpl, val debugProcess: DebugProcessImpl, item: StackFrameItem) :
CoroutineStackFrame(debugProcess, item) {
override fun computeChildren(node: XCompositeNode) {
val fakeStackFrame = debugProcess.invokeInManagerThread {
val skipCoroutineFrame = SkipCoroutineStackFrameProxyImpl(frame)
debugProcess.positionManager.createStackFrame(skipCoroutineFrame, debugProcess, frame.location())
}
fakeStackFrame?.computeChildren(node)
// super.computeChildren(node)
}
}
open class CoroutineStackFrame(debugProcess: DebugProcessImpl, val item: StackFrameItem, val realStackFrame: XStackFrame? = null) :
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 computeChildren(node: XCompositeNode) {
if (realStackFrame != null)
realStackFrame.computeChildren(node)
else
super.computeChildren(node)
}
override fun getCaptionAboveOf() = "CoroutineExit"
override fun hasSeparatorAbove(): Boolean =
false
}
@@ -6,195 +6,128 @@
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.coroutineDebuggerTraceEnabled
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.FieldVariable
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
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
import org.jetbrains.kotlin.idea.debugger.safeLocation
data class ContinuationHolder(val continuation: ObjectReference, val context: DefaultExecutionContext) {
val log by logger
class ContinuationHolder(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 getCoroutineInfoData(): CoroutineInfoData? {
fun extractCoroutineInfoData(continuation: ObjectReference): CoroutineInfoData? {
try {
return collectCoroutineInfo()
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 stack frame.", e)
}
return null
}
private fun collectCoroutineInfo(): CoroutineInfoData? {
val consumer = mutableListOf<CoroutineStackFrameItem>()
var completion = this
while (completion.isBaseContinuationImpl()) {
val coroutineStackFrame = context.debugProcess.invokeInManagerThread {
completion.createLocation()
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
}
return CoroutineInfoData.lookup(completion.value(), context, consumer)
return CoroutineInfoData(realState, stackFrameItems, creationStackTrace)
}
private fun createLocation(): DefaultCoroutineStackFrameItem? {
val stackTraceElementMirror = debugMetadata?.getStackTraceElement(continuation, context) ?: return null
val stackTraceElement = stackTraceElementMirror.stackTraceElement()
val locationClass = context.findClassSafe(stackTraceElement.className) ?: return null
val generatedLocation =
GeneratedLocation(context.debugProcess, locationClass, stackTraceElement.methodName, stackTraceElement.lineNumber)
val spilledVariables = getSpilledVariables() ?: emptyList()
return DefaultCoroutineStackFrameItem(generatedLocation, spilledVariables)
}
fun getSpilledVariables(): List<XNamedValue>? {
val variables: List<JavaValue> = context.debugProcess.invokeInManagerThread {
debugMetadata?.getSpilledVariableFieldMapping(continuation, context)?.mapNotNull {
fieldVariableToNamedValue(it, this)
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)
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)
}
} ?: emptyList()
return variables
}
fun fieldVariableToNamedValue(fieldVariable: FieldVariable, continuation: ContinuationHolder): JavaValue {
val valueDescriptor = ContinuationValueDescriptorImpl(
context.project,
continuation,
fieldVariable.fieldName,
fieldVariable.variableName
)
return JavaValue.create(
null,
valueDescriptor,
context.evaluationContext,
context.debugProcess.xdebugProcess!!.nodeManager,
false
)
}
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()
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)
}
companion object {
val log by logger
fun coroutineExitFrame(
frame: StackFrameProxyImpl,
suspendContext: SuspendContextImpl?
): XStackFrame? {
suspendContext ?: return null
return suspendContext.invokeInManagerThread {
if (frame.location().isPreFlight() || frame.location().isPreExitFrame()) {
if (coroutineDebuggerTraceEnabled())
log.trace("Entry frame found: ${formatLocation(frame.location())}")
val leftThreadStack = leftThreadStack(frame) ?: return@invokeInManagerThread null
lookupContinuation(suspendContext, frame, leftThreadStack)
} 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 leftThreadStack(frame: StackFrameProxyImpl): List<StackFrameProxyImpl>? {
var 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)
return null
return frames.drop(indexOfCurrentFrame)
} else
return null
}
fun lookupContinuation(
suspendContext: SuspendContextImpl,
previousFrame: StackFrameProxyImpl, // invokeSuspend
framesLeft: List<StackFrameProxyImpl>
): CoroutinePreflightStackFrame? {
val continuation =
if (previousFrame.safeLocation()?.method()?.isSuspendLambda() ?: false ||
previousFrame.safeLocation()?.method()?.isContinuation() ?: false
)
getThisContinuation(previousFrame)
else
null
if (continuation != null) {
val context = suspendContext.executionContext() ?: return null
val coroutineStackTrace = ContinuationHolder(continuation, context).getCoroutineInfoData() ?: return null
return CoroutinePreflightStackFrame.preflight(
previousFrame,
coroutineStackTrace,
framesLeft
)
} else
return null
}
private fun getCompletionContinuation(previousFrame: StackFrameProxyImpl?) =
previousFrame?.completion1VariableValue()
private fun getThisContinuation(previousFrame: StackFrameProxyImpl?): ObjectReference? =
previousFrame?.thisVariableValue()
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?,
): ContinuationHolder? {
var continuation = initialContinuation ?: return null
val executionContext = context.executionContext() ?: return null
do {
continuation = getNextFrame(executionContext, continuation) ?: return null
} while (continuation.type().isBaseContinuationImpl() /* && position != classLine */)
return if (continuation.type().isBaseContinuationImpl())
ContinuationHolder(continuation, executionContext)
else
return null
}
}
}
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
)
}
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.kotlin.idea.debugger.coroutine.command.CoroutineBuilder
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
@@ -44,8 +44,6 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
return CoroutineNoLibraryProxy(executionContext)
return null
}
fun frameBuilder() = CoroutineBuilder(suspendContext)
}
fun standaloneCoroutineDebuggerEnabled() = Registry.`is`("kotlin.debugger.coroutines.standalone")
@@ -5,17 +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 com.intellij.xdebugger.frame.XNamedValue
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.DebugMetadata
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.JavaLangMirror
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
@@ -24,6 +19,7 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution
val log by logger
private val debugProbesImpl = DebugProbesImpl(executionContext)
private val locationCache = LocationCache(executionContext)
private val debugMetadata: DebugMetadata? = DebugMetadata.instance(executionContext)
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
val result = debugProbesImpl.dumpCoroutinesInfo(executionContext)
@@ -33,10 +29,14 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution
fun mapToCoroutineInfoData(mirror: MirrorOfCoroutineInfo): CoroutineInfoData? {
val cnis = CoroutineNameIdState.instance(mirror)
val stackTrace = mirror.enchancedStackTrace?.mapNotNull { it.stackTraceElement() } ?: emptyList()
var stackFrames = findStackFrames(stackTrace)
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.toMutableList(),
stackFrames.restoredStackFrames,
stackFrames.creationStackFrames,
mirror.lastObservedThread,
mirror.lastObservedFrame
@@ -52,13 +52,18 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution
}
}
private fun findStackFrames(frames: List<StackTraceElement>): CoroutineStackFrames {
private fun findStackFrames(
frames: List<StackTraceElement>,
variables: List<XNamedValue>
): CoroutineStackFrames {
val index = frames.indexOfFirst { it.isCreationSeparatorFrame() }
return CoroutineStackFrames(frames.take(index).map {
SuspendCoroutineStackFrameItem(it, locationCache.createLocation(it))
}, frames.subList(index + 1, frames.size).map {
CreationCoroutineStackFrameItem(it, locationCache.createLocation(it))
})
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(
@@ -5,17 +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.CoroutineInfo
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
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) :
@@ -43,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)
@@ -79,11 +73,11 @@ 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), "")
@@ -96,32 +90,6 @@ class CoroutineLibraryAgentProxy(private val debugProbesClsRef: ClassType, priva
)
}
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(executionContext)
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
val vm = executionContext.vm
@@ -51,7 +55,7 @@ class CoroutineNoLibraryProxy(val executionContext: DefaultExecutionContext) : C
): CoroutineInfoData? {
val mirror = ccMirrorProvider.mirror(dispatchedContinuation, executionContext) ?: return null
val continuation = mirror.delegate?.continuation ?: return null
return ContinuationHolder(continuation, executionContext).getCoroutineInfoData()
return holder.extractCoroutineInfoData(continuation)
}
private fun dispatchedContinuation(resultList: MutableList<CoroutineInfoData>): Boolean {
@@ -68,10 +72,10 @@ 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
return ContinuationHolder(initialContinuation, executionContext).getCoroutineInfoData()
return holder.extractCoroutineInfoData(initialContinuation)
}
}
@@ -1,101 +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.GeneratedLocation
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.intellij.xdebugger.frame.XCompositeNode
import com.intellij.xdebugger.frame.XValueChildrenList
import com.sun.jdi.Location
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.CreationCoroutineStackFrame
import org.jetbrains.kotlin.idea.debugger.invokeInManagerThread
import org.jetbrains.kotlin.idea.debugger.safeLineNumber
import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod
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>,
) : KotlinStackFrame(stackFrameDescriptorImpl), JVMStackFrameInfoProvider {
override fun computeChildren(node: XCompositeNode) {
val childrenList = XValueChildrenList()
val firstRestoredCoroutineStackFrameItem = coroutineInfoData.stackTrace.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,
coroutineInfoData: CoroutineInfoData,
originalFrames: List<StackFrameProxyImpl>
): CoroutinePreflightStackFrame? {
val descriptor = createFirstRestoredFrame(invokeSuspendFrame, coroutineInfoData)
return CoroutinePreflightStackFrame(
coroutineInfoData,
descriptor,
originalFrames.filter { ! isInvokeSuspendNegativeLineMethodFrame(it) }
)
}
fun createFirstRestoredFrame(
invokeSuspendFrame: StackFrameProxyImpl,
coroutineInfoData: CoroutineInfoData
): StackFrameDescriptorImpl {
if (coroutineInfoData.stackTrace.size >= 2) {
// assume firstFrame is invokeSuspend and second is resumeWith
val fisrtRestoredFrame = coroutineInfoData.stackTrace.removeAt(0)
val secondRestoredFrame = coroutineInfoData.stackTrace.removeAt(0)
println(formatLocation(invokeSuspendFrame.location()))
println(formatLocation(fisrtRestoredFrame.location))
println(formatLocation(secondRestoredFrame.location))
val descriptor = StackFrameDescriptorImpl(
LocationStackFrameProxyImpl(secondRestoredFrame.location, invokeSuspendFrame), MethodsTracker()
)
return descriptor
} else {
return StackFrameDescriptorImpl(invokeSuspendFrame, MethodsTracker())
}
}
private fun formatLocation(location: Location): String {
return "${location.method().name()}:${location.lineNumber()}, ${location.method().declaringType()}"
}
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
}
}
@@ -17,13 +17,13 @@ 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 = findLocation(
fun createLocation(stackTraceElement: StackTraceElement): Location = createLocation(
ContainerUtil.getFirstItem(classesByName[stackTraceElement.className]),
stackTraceElement.methodName,
stackTraceElement.lineNumber
)
private fun findLocation(
fun createLocation(
type: ReferenceType?,
methodName: String,
line: Int
@@ -17,6 +17,5 @@ class LocationStackFrameProxyImpl(val location: Location, frame: StackFrameProxy
}
}
class SkipCoroutineStackFrameProxyImpl(frame: StackFrameProxyImpl) :
StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom)
@@ -6,13 +6,15 @@
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.evaluate.DefaultExecutionContext
class DebugProbesImpl(context: DefaultExecutionContext) :
class DebugProbesImpl internal constructor(context: DefaultExecutionContext) :
BaseMirror<MirrorOfDebugProbesImpl>("kotlinx.coroutines.debug.internal.DebugProbesImpl", context) {
val javaLangListMirror = JavaUtilList(context)
val javaLangListMirror = JavaUtilAbstractCollection(context)
val stackTraceElement = StackTraceElement(context)
val coroutineInfo = CoroutineInfo(this, context)
val debugProbesCoroutineOwner = DebugProbesImpl_CoroutineOwner(coroutineInfo, context)
val instance = staticObjectValue("INSTANCE")
val isInstalledMethod = makeMethod("isInstalled\$kotlinx_coroutines_debug", "()Z")
val isInstalledValue = booleanValue(instance, isInstalledMethod, context)
@@ -41,20 +43,46 @@ class DebugProbesImpl(context: DefaultExecutionContext) :
return referenceList.values.mapNotNull { coroutineInfo.mirror(it, context) }
}
fun getCoroutineInfo(input: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? {
// kotlinx.coroutines.debug.internal.DebugProbesImpl$CoroutineOwner
val delegate = input?.referenceType()?.fieldByName("info") ?: return null
val coroutine = input.getValue(delegate) as? ObjectReference
return coroutineInfo.mirror(coroutine, context)
fun getCoroutineInfo(value: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? {
val coroutineOwner = debugProbesCoroutineOwner.mirror(value, context)
return coroutineOwner?.coroutineInfo
}
companion object {
fun instance(context: DefaultExecutionContext) =
try {
DebugProbesImpl(context)
} catch (e: IllegalStateException) {
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(val debugProbesImplMirror: DebugProbesImpl, context: DefaultExecutionContext) :
BaseMirror<MirrorOfCoroutineInfo>("kotlinx.coroutines.debug.CoroutineInfo", context) {
val javaLangMirror = JavaLangMirror(context)
val javaLangListMirror = JavaUtilList(context)
val javaLangListMirror = JavaUtilAbstractCollection(context)
private val coroutineContextMirror = CoroutineContext(context)
private val coroutineStackFrameMirror = CoroutineStackFrame(context)
private val stackTraceElement = StackTraceElement(context)
@@ -182,6 +210,9 @@ data class MirrorOfStackTraceElement(
val lineNumber: Int?,
val format: Byte?
) {
fun isInvokeSuspend() =
"invokeSuspend" == methodName
fun stackTraceElement() =
java.lang.StackTraceElement(
declaringClass,
@@ -6,7 +6,7 @@
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
@@ -24,8 +24,8 @@ abstract class BaseMirror<T>(val name: String, context: DefaultExecutionContext)
fun makeMethod(methodName: String, signature: String): Method? =
cls?.let { it.methodsByName(methodName, signature).single() }
fun isCompatible(value: ObjectReference) =
value.referenceType().isSubTypeOrSame(name)
fun isCompatible(value: ObjectReference?) =
value?.let { it.referenceType().isSubTypeOrSame(name) } ?: false
fun mirror(value: ObjectReference?, context: DefaultExecutionContext): T? {
value ?: return null
@@ -56,16 +56,24 @@ abstract class BaseMirror<T>(val name: String, context: DefaultExecutionContext)
}
fun stringValue(value: ObjectReference, field: Field?) =
(value.getValue(field) as? StringReference)?.value()
field?.let {
(value.getValue(it) as? StringReference)?.value()
}
fun byteValue(value: ObjectReference, field: Field?) =
(value.getValue(field) as? ByteValue)?.value()
field?.let {
(value.getValue(it) as? ByteValue)?.value()
}
fun threadValue(value: ObjectReference, field: Field?) =
value.getValue(field) as? ThreadReference
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() }
method?.let {
(context.invokeMethod(value, it, emptyList()) as? StringReference)?.value()
}
fun objectValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value) =
value?.let {
@@ -9,29 +9,76 @@ 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.coroutine.proxy.isBaseContinuationImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isBaseContinuationImpl
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class DebugMetadata(context: DefaultExecutionContext) :
class DebugMetadata internal constructor(context: DefaultExecutionContext) :
BaseMirror<MirrorOfDebugProbesImpl>("kotlin.coroutines.jvm.internal.DebugMetadataKt", context) {
val getStackTraceElementMethod = makeMethod("getStackTraceElement")
val getSpilledVariableFieldMappingMethod = makeMethod("getSpilledVariableFieldMapping", "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;")
val stackTraceElement = StackTraceElement(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? {
// @TODO fix this
return null
}
fun getStackTraceElement(value: ObjectReference, context: DefaultExecutionContext): MirrorOfStackTraceElement? {
if (value.referenceType().isBaseContinuationImpl()) {
val stackTraceObjectReference = staticMethodValue(getStackTraceElementMethod, context, value) ?: return null
return stackTraceElement.mirror(stackTraceObjectReference, context)
} else
return null
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 getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext): List<FieldVariable> {
val getSpilledVariableFieldMappingReference = staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference ?: return emptyList()
fun getStackTraceElement(value: ObjectReference, context: DefaultExecutionContext) =
staticMethodValue(getStackTraceElementMethod, context, value)
fun getSpilledVariableFieldMapping(value: ObjectReference, context: DefaultExecutionContext) =
staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference
companion object {
fun instance(context: DefaultExecutionContext) =
try {
DebugMetadata(context)
} catch (e: IllegalStateException) {
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>()
@@ -46,15 +93,12 @@ class DebugMetadata(context: DefaultExecutionContext) :
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: return null
return FieldVariable(fieldName, variableName)
}
companion object {
fun instance(context: DefaultExecutionContext) =
try {
DebugMetadata(context)
} catch (e : IllegalStateException) {
null
}
}
}
data class FieldVariable(val fieldName: String, val variableName: String)
data class MirrorOfBaseContinuationImpl(
val that: ObjectReference,
val stackTraceElement: MirrorOfStackTraceElement?,
val fieldVariables: List<FieldVariable>,
val nextContinuation: ObjectReference?,
val coroutineOwner: ObjectReference?
)
@@ -61,21 +61,33 @@ class JavaLangMirror(context: DefaultExecutionContext) {
(instance.getValue(declaringClassFieldRef) as? StringReference)?.value() ?: ""
}
class JavaUtilList(context: DefaultExecutionContext) :
BaseMirror<MirrorOfJavaLangList>("java.util.List", context) {
class JavaUtilAbstractCollection(context: DefaultExecutionContext) :
BaseMirror<MirrorOfJavaLangAbstractCollection>("java.util.AbstractCollection", context) {
val abstractList = JavaUtilAbstractList(context)
val sizeMethod = makeMethod("size")
val getMethod = makeMethod("get")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfJavaLangList? {
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfJavaLangAbstractCollection? {
val list = mutableListOf<ObjectReference>()
val size = intValue(value, sizeMethod, context) ?: 0
for (it in 0 until size) {
val reference = objectValue(value, getMethod, context, context.vm.mirrorOf(it)) ?: continue
for (index in 0 until size) {
val reference = abstractList.get(value, index, context) ?: continue
list.add(reference)
}
return MirrorOfJavaLangList(value, list)
return MirrorOfJavaLangAbstractCollection(value, list)
}
}
data class MirrorOfJavaLangList(val that: ObjectReference, val values: List<ObjectReference>)
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,177 @@
/*
* 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 org.jetbrains.kotlin.idea.debugger.safeLineNumber
import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod
class CoroutineFrameBuilder {
companion object {
val log by logger
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) {
coroutineStackFrameList.add(buildRealStackFrameItem(preflightStackFrame.stackFrameProxy,))
val doubleFrameList = build(preflightStackFrame, suspendContext)
coroutineStackFrameList.addAll(doubleFrameList.stackTrace)
return DoubleFrameList(coroutineStackFrameList, doubleFrameList.creationStackTrace)
} else {
coroutineStackFrameList.add(buildRealStackFrameItem(runningStackFrameProxy,))
}
}
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
stackFrames.addAll(preflightFrame.threadPreCoroutineFrames.drop(1).mapIndexedNotNull { index, stackFrameProxyImpl ->
// if (index == 0)
// PreCoroutineStackFrameItem(stackFrameProxyImpl, firstRestoredFrame) // get location and variables from restored part
// else
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()
return RunningCoroutineStackFrameItem(frame, location)
}
/**
* 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()) {
if (coroutineDebuggerTraceEnabled())
ContinuationHolder.log.debug("Entry frame found: ${frame.format()}")
lookupContinuation(suspendContext, frame, sem)
} else
null
}
}
private fun filterNegativeLineNumberInvokeSuspendFrames(frame: StackFrameProxyImpl): Boolean {
val method = frame.safeLocation()?.safeMethod() ?: return false
return method.isInvokeSuspend() && frame.safeLocation()?.safeLineNumber() ?: 0 < 0
}
fun lookupContinuation(
suspendContext: SuspendContextImpl,
frame: StackFrameProxyImpl,
mode: SuspendExitMode
): CoroutinePreflightStackFrame? {
if (!mode.isCoroutineFound())
return null
val theFollowingFrames = theFollowingFrames(frame) ?: emptyList()
if (mode.isSuspendMethodParameter()) {
if (theFollowingFrames.isNotEmpty()) {
// have to check next frame if that's invokeSuspend:-1 before proceed, otherwise skip
val theFollowingFrame = theFollowingFrames.first()
val theFollowingMode = theFollowingFrame.location().isPreFlight()
if (theFollowingMode != SuspendExitMode.SUSPEND_METHOD)
return null // break, that's not the exit, perhaps the following one?
} else
return 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 coroutineInfo = ContinuationHolder(context).extractCoroutineInfoData(continuation) ?: return null
return preflight(frame, theFollowingFrames, coroutineInfo, mode)
}
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,7 +3,7 @@
* 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
@@ -17,10 +17,12 @@ 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;"
@@ -30,12 +32,21 @@ fun Method.isContinuation() =
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() && safeLineNumber() == -1)
return SuspendExitMode.SUSPEND_METHOD
return SuspendExitMode.NONE
}
fun ReferenceType.isContinuation() =
@@ -53,8 +64,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,8 +75,8 @@ fun StackFrameProxyImpl.variableValue(variableName: String): ObjectReference? {
fun StackFrameProxyImpl.completionVariableValue(): ObjectReference? =
variableValue("completion")
fun StackFrameProxyImpl.completion1VariableValue(): ObjectReference? =
variableValue("completion")
fun StackFrameProxyImpl.continuationVariableValue(): ObjectReference? =
variableValue("\$continuation")
fun StackFrameProxyImpl.thisVariableValue(): ObjectReference? =
this.thisObject()
@@ -86,7 +97,7 @@ 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)
@@ -108,17 +119,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)) }
@@ -134,3 +134,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
}
}
@@ -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,11 +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.ClassesByNameProvider
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.ide.CommonActionsManager
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionManager
@@ -36,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
@@ -214,18 +211,14 @@ class XCoroutineView(val project: Project, val session: XDebugSession) :
override fun computeChildren(node: XCompositeNode) {
managerThreadExecutor.on(suspendContext).schedule {
val debugProbesProxy = CoroutineDebugProbesProxy(suspendContext)
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
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,11 +58,10 @@ 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
)
isCurrentContext) } ?: return false
createStackAndSetFrame(threadProxy, { executionStack.createStackFrame(stackFrameItem.frame) }, isCurrentContext)
}
is CreationCoroutineStackFrameItem -> {
@@ -73,15 +69,17 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
val threadProxy = suspendContext.thread ?: return false
createStackAndSetFrame(threadProxy, {
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), emptyList(), position)
SyntheticStackFrame(stackFrameItem.descriptor(realFrame), emptyList(), position)
})
}
is SuspendCoroutineStackFrameItem -> {
val threadProxy = suspendContext.thread ?: return false
val lastFrame = valueContainer.infoData.lastObservedFrameFieldRef ?: return false
val position = stackFrameItem.location.findPosition(session.project)
?: return false
createStackAndSetFrame(threadProxy, {
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
createSyntheticStackFrame(suspendContext, stackFrameItem, realFrame, lastFrame)
SyntheticStackFrame(stackFrameItem.descriptor(realFrame), stackFrameItem.spilledVariables, position)
})
}
is RestoredCoroutineStackFrameItem -> {
@@ -89,16 +87,21 @@ class XDebuggerTreeSelectedNodeListener(val session: XDebugSession, val tree: XD
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
createStackAndSetFrame(threadProxy, {
val realFrame = threadProxy.forceFrames().first() ?: return@createStackAndSetFrame null
SyntheticStackFrame(stackFrameItem.emptyDescriptor(realFrame), stackFrameItem.spilledVariables, position)
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 -> {
@@ -124,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?) {