diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt index f986271392b..895f68a2962 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineLibraryAgent2Proxy.kt @@ -17,12 +17,12 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecutionContext) : CoroutineInfoProvider { val log by logger - private val debugProbesImpl = DebugProbesImpl(executionContext) + private val debugProbesImpl = DebugProbesImpl.instance(executionContext) private val locationCache = LocationCache(executionContext) private val debugMetadata: DebugMetadata? = DebugMetadata.instance(executionContext) override fun dumpCoroutinesInfo(): List { - val result = debugProbesImpl.dumpCoroutinesInfo(executionContext) + val result = debugProbesImpl?.dumpCoroutinesInfo(executionContext) ?: emptyList() return result.mapNotNull { mapToCoroutineInfoData(it) } } @@ -45,7 +45,7 @@ class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecution fun isInstalled(): Boolean { try { - return debugProbesImpl.isInstalledValue ?: false + return debugProbesImpl?.isInstalledValue ?: false } catch (e: Exception) { log.error("Exception happened while checking agent status.", e) return false diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/baseDynamicMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/baseDynamicMirror.kt new file mode 100644 index 00000000000..111261ed6e4 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/baseDynamicMirror.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror + +import com.sun.jdi.* +import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame +import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger +import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext + +abstract class BaseDynamicMirror(val value: ObjectReference, val name: String, val context: DefaultExecutionContext) { + val log by logger + + val cls: ReferenceType? = value.referenceType() + + fun makeField(fieldName: String): Field? = + cls?.let { it.fieldByName(fieldName) } + + fun makeMethod(methodName: String): Method? = + cls?.let { it.methodsByName(methodName).single() } + + fun makeMethod(methodName: String, signature: String): Method? = + cls?.let { it.methodsByName(methodName, signature).single() } + + fun isCompatible(value: ObjectReference?) = + value?.let { it.referenceType().isSubTypeOrSame(name) } ?: false + + fun mirror(): T? { + if (!isCompatible(value)) { + log.trace("Value ${value.referenceType()} is not compatible with $name.") + return null + } else + return fetchMirror(value, context) + } + + fun staticObjectValue(fieldName: String): ObjectReference? { + val keyFieldRef = makeField(fieldName) + return cls?.let { it.getValue(keyFieldRef) as? ObjectReference } + } + + fun staticMethodValue(instance: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value?) = + instance?.let { + method?.let { m -> + context.invokeMethod(it, m, values.asList()) as? ObjectReference + } + } + + fun staticMethodValue(method: Method?, context: DefaultExecutionContext, vararg values: Value?) = + cls?.let { + method?.let { + if (cls is ClassType) + context.invokeMethodSafe(cls, method, values.asList()) as? ObjectReference + else + null + } + } + + fun stringValue(value: ObjectReference, field: Field?) = + field?.let { + (value.getValue(it) as? StringReference)?.value() + } + + fun byteValue(value: ObjectReference, field: Field?) = + field?.let { + (value.getValue(it) as? ByteValue)?.value() + } + + fun threadValue(value: ObjectReference, field: Field?) = + field?.let { + value.getValue(it) as? ThreadReference + } + + fun stringValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext) = + method?.let { + (context.invokeMethod(value, it, emptyList()) as? StringReference)?.value() + } + + fun objectValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value) = + value?.let { + method?.let { + context.invokeMethodAsObject(value, method, *values) + } + } + + fun longValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) = + method?.let { (context.invokeMethod(value, it, values.asList()) as? LongValue)?.longValue() } + + fun intValue(value: ObjectReference, method: Method?, context: DefaultExecutionContext, vararg values: Value) = + method?.let { (context.invokeMethod(value, it, values.asList()) as? IntegerValue)?.intValue() } + + fun booleanValue(value: ObjectReference?, method: Method?, context: DefaultExecutionContext, vararg values: Value): Boolean? { + value ?: return null + method ?: return null + return (context.invokeMethod(value, method, values.asList()) as? BooleanValue)?.booleanValue() + } + + fun objectValue(value: ObjectReference, field: Field?) = + field?.let { value.getValue(it) as ObjectReference? } + + fun intValue(value: ObjectReference, field: Field?) = + field?.let { (value.getValue(it) as? IntegerValue)?.intValue() } + + fun longValue(value: ObjectReference, field: Field?) = + field?.let { (value.getValue(it) as? LongValue)?.longValue() } + + fun booleanValue(value: ObjectReference?, field: Field?) = + field?.let { (value?.getValue(field) as? BooleanValue)?.booleanValue() } + + protected abstract fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): T? +} diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt index 85957464da6..3749e821e32 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesContextMirror.kt @@ -67,8 +67,8 @@ class CoroutineId(context: DefaultExecutionContext) : ContextKey("kotlinx. override fun key() = key } -class Job(context: DefaultExecutionContext) : ContextKey("kotlinx.coroutines.Job", context) { - val key = staticObjectValue("Key") +class Job(context: DefaultExecutionContext) : ContextKey("kotlinx.coroutines.Job\$Key", context) { + val key = staticObjectValue("\$\$INSTANCE") override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): ObjectReference? { return value diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt index d1c50d06932..178ef9ef5e6 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt @@ -7,16 +7,18 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror import com.sun.jdi.* import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame +import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext -class DebugProbesImpl internal constructor(context: DefaultExecutionContext) : +class DebugProbesImpl private constructor(context: DefaultExecutionContext) : BaseMirror("kotlinx.coroutines.debug.internal.DebugProbesImpl", context) { val javaLangListMirror = JavaUtilAbstractCollection(context) val stackTraceElement = StackTraceElement(context) - val coroutineInfo = CoroutineInfo(this, context) + val coroutineInfo = CoroutineInfo.instance(this, context) ?: throw IllegalStateException("CoroutineInfo implementation not found.") val debugProbesCoroutineOwner = DebugProbesImpl_CoroutineOwner(coroutineInfo, context) val instance = staticObjectValue("INSTANCE") val isInstalledMethod = makeMethod("isInstalled\$kotlinx_coroutines_debug", "()Z") + ?: makeMethod("isInstalled\$kotlinx_coroutines_core", "()Z") ?: throw IllegalStateException("isInstalledMethod not found") val isInstalledValue = booleanValue(instance, isInstalledMethod, context) val enhanceStackTraceWithThreadDumpMethod = makeMethod("enhanceStackTraceWithThreadDump") val dumpMethod = makeMethod("dumpCoroutinesInfo", "()Ljava/util/List;") @@ -49,10 +51,13 @@ class DebugProbesImpl internal constructor(context: DefaultExecutionContext) : } companion object { + val log by logger + fun instance(context: DefaultExecutionContext) = try { DebugProbesImpl(context) } catch (e: IllegalStateException) { + log.warn("Attempt to access DebugProbesImpl", e) null } } @@ -79,12 +84,15 @@ data class MirrorOfCoroutineOwner(val that: ObjectReference, val coroutineInfo: data class MirrorOfDebugProbesImpl(val that: ObjectReference, val instance: ObjectReference?, val isInstalled: Boolean?) -class CoroutineInfo(val debugProbesImplMirror: DebugProbesImpl, context: DefaultExecutionContext) : - BaseMirror("kotlinx.coroutines.debug.CoroutineInfo", context) { +class CoroutineInfo private constructor( + val debugProbesImplMirror: DebugProbesImpl, + context: DefaultExecutionContext, + val className: String = AGENT_134_CLASS_NAME +) : + BaseMirror(className, context) { val javaLangMirror = JavaLangMirror(context) val javaLangListMirror = JavaUtilAbstractCollection(context) private val coroutineContextMirror = CoroutineContext(context) - private val coroutineStackFrameMirror = CoroutineStackFrame(context) private val stackTraceElement = StackTraceElement(context) private val contextFieldRef = makeField("context") private val creationStackBottom = makeField("creationStackBottom") @@ -96,13 +104,30 @@ class CoroutineInfo(val debugProbesImplMirror: DebugProbesImpl, context: Default private val lastObservedFrameField = makeField("lastObservedFrame") private val lastObservedThreadField = makeField("lastObservedThread") + companion object { + val log by logger + const val AGENT_134_CLASS_NAME = "kotlinx.coroutines.debug.CoroutineInfo" + const val AGENT_135_AND_UP_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugCoroutineInfo" + + fun instance(debugProbesImplMirror: DebugProbesImpl, context: DefaultExecutionContext): CoroutineInfo? { + val classType = context.findClassSafe(AGENT_134_CLASS_NAME) ?: context.findClassSafe(AGENT_135_AND_UP_CLASS_NAME) ?: return null + try { + return CoroutineInfo(debugProbesImplMirror, context, classType.name()) + } catch (e: IllegalStateException) { + log.warn("coroutine-debugger: $classType not found", e) + return null + } + } + } + override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineInfo { val state = objectValue(value, stateMethod, context)?.let { stringValue(it, javaLangMirror.toString, context) } val coroutineContext = coroutineContextMirror.mirror(objectValue(value, contextFieldRef), context) val creationStackBottomObjectReference = objectValue(value, creationStackBottom) - val creationStackBottom = coroutineStackFrameMirror.mirror(creationStackBottomObjectReference, context) + val creationStackBottom = + creationStackBottomObjectReference?.let { CoroutineStackFrame(creationStackBottomObjectReference, context).mirror() } val sequenceNumber = longValue(value, sequenceNumberField) val creationStackTraceList = objectValue(value, creationStackTraceMethod, context) val creationStackTraceMirror = javaLangListMirror.mirror(creationStackTraceList, context) @@ -127,9 +152,9 @@ class CoroutineInfo(val debugProbesImplMirror: DebugProbesImpl, context: Default lastObservedFrame ) } - } + data class MirrorOfCoroutineInfo( val that: ObjectReference, val context: MirrorOfCoroutineContext?, @@ -142,8 +167,8 @@ data class MirrorOfCoroutineInfo( val lastObservedFrame: ObjectReference? ) -class CoroutineStackFrame(context: DefaultExecutionContext) : - BaseMirror("kotlin.coroutines.jvm.internal.CoroutineStackFrame", context) { +class CoroutineStackFrame(value: ObjectReference, context: DefaultExecutionContext) : + BaseDynamicMirror(value, "kotlin.coroutines.jvm.internal.CoroutineStackFrame", context) { private val stackTraceElementMirror = StackTraceElement(context) private val callerFrameMethod = makeMethod("getCallerFrame") private val getStackTraceElementMethod = makeMethod("getStackTraceElement") @@ -151,7 +176,7 @@ class CoroutineStackFrame(context: DefaultExecutionContext) : override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineStackFrame? { val objectReference = objectValue(value, callerFrameMethod, context) val callerFrame = if (objectReference is ObjectReference) - this.mirror(objectReference, context) else null + CoroutineStackFrame(objectReference, context).mirror() else null val stackTraceElementReference = objectValue(value, getStackTraceElementMethod, context) val stackTraceElement = if (stackTraceElementReference is ObjectReference) stackTraceElementMirror.mirror(stackTraceElementReference, context) else null diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt index 1583a336d3d..2c37f29ebf3 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesMirror.kt @@ -9,20 +9,20 @@ import com.sun.jdi.* import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext - +import java.lang.IllegalStateException abstract class BaseMirror(val name: String, context: DefaultExecutionContext) { val log by logger - protected val cls = context.findClassSafe(name) + protected val cls = context.findClassSafe(name) ?: throw IllegalStateException("coroutine-debugger: class $name not found.") fun makeField(fieldName: String): Field? = cls?.let { it.fieldByName(fieldName) } fun makeMethod(methodName: String): Method? = - cls?.let { it.methodsByName(methodName).single() } + cls?.let { it.methodsByName(methodName).singleOrNull() } fun makeMethod(methodName: String, signature: String): Method? = - cls?.let { it.methodsByName(methodName, signature).single() } + cls?.let { it.methodsByName(methodName, signature).singleOrNull() } fun isCompatible(value: ObjectReference?) = value?.let { it.referenceType().isSubTypeOrSame(name) } ?: false diff --git a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt index 5ad93c174bc..9d8cfb890e1 100644 --- a/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt +++ b/idea/jvm-debugger/jvm-debugger-coroutine/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/debugMetadata.kt @@ -10,9 +10,10 @@ import com.sun.jdi.ClassType import com.sun.jdi.ObjectReference import com.sun.jdi.StringReference import org.jetbrains.kotlin.idea.debugger.coroutine.util.isBaseContinuationImpl +import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext -class DebugMetadata internal constructor(context: DefaultExecutionContext) : +class DebugMetadata private constructor(context: DefaultExecutionContext) : BaseMirror("kotlin.coroutines.jvm.internal.DebugMetadataKt", context) { private val getStackTraceElementMethod = makeMethod("getStackTraceElement") private val getSpilledVariableFieldMappingMethod = @@ -42,10 +43,13 @@ class DebugMetadata internal constructor(context: DefaultExecutionContext) : staticMethodValue(getSpilledVariableFieldMappingMethod, context, value) as? ArrayReference companion object { + val log by logger + fun instance(context: DefaultExecutionContext) = try { DebugMetadata(context) } catch (e: IllegalStateException) { + log.warn("Attempt to access DebugMetadata", e) null } }