Use caching 'findClass()' instead of raw 'Class.forName()' call in async stack trace provider (KT-30268)
This commit is contained in:
+67
-101
@@ -6,7 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
import com.intellij.debugger.DebuggerContext
|
import com.intellij.debugger.DebuggerContext
|
||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
|
||||||
import com.intellij.debugger.engine.JavaStackFrame
|
import com.intellij.debugger.engine.JavaStackFrame
|
||||||
import com.intellij.debugger.engine.JavaValue
|
import com.intellij.debugger.engine.JavaValue
|
||||||
import com.intellij.debugger.engine.SuspendContextImpl
|
import com.intellij.debugger.engine.SuspendContextImpl
|
||||||
@@ -14,42 +13,18 @@ import com.intellij.debugger.engine.evaluation.EvaluateException
|
|||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
import com.intellij.debugger.jdi.GeneratedLocation
|
import com.intellij.debugger.jdi.GeneratedLocation
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
|
|
||||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||||
import com.intellij.xdebugger.frame.XNamedValue
|
import com.intellij.xdebugger.frame.XNamedValue
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
|
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
|
||||||
|
|
||||||
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||||
private companion object {
|
private companion object {
|
||||||
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
||||||
|
|
||||||
fun ContextBase.classByName(name: String): ReferenceType? {
|
|
||||||
val classClass = virtualMachine.classesByName(Class::class.java.name).firstIsInstanceOrNull<ClassType>() ?: return null
|
|
||||||
val forNameMethod = classClass.methodsByName("forName")
|
|
||||||
.firstOrNull { it.signature() == "(Ljava/lang/String;)Ljava/lang/Class;" }
|
|
||||||
?: return null
|
|
||||||
|
|
||||||
try {
|
|
||||||
val args = listOf(virtualMachine.mirrorOf(name))
|
|
||||||
val result = debugProcess.invokeMethod(evaluationContext, classClass, forNameMethod, args)
|
|
||||||
|
|
||||||
if (result is ClassObjectReference) {
|
|
||||||
return result.reflectedType()
|
|
||||||
}
|
|
||||||
} catch (e: InvocationException) {
|
|
||||||
// Ignore ClassNotFoundException
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
LOG.error(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
||||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
||||||
return type
|
return type
|
||||||
@@ -73,19 +48,22 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
|
|||||||
if (currentThread == null || !currentThread.isSuspended || !currentThread.isAtBreakpoint) {
|
if (currentThread == null || !currentThread.isSuspended || !currentThread.isAtBreakpoint) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val contextBase = ContextBase(suspendContext, frameProxy)
|
|
||||||
val debugMetadataKtType = contextBase.classByName(DEBUG_METADATA_KT) as? ClassType ?: return null
|
|
||||||
|
|
||||||
val context = Context(suspendContext, frameProxy, method, debugMetadataKtType)
|
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
||||||
return context.getAsyncStackTraceForSuspendLambda() ?: context.getAsyncStackTraceForSuspendFunction()
|
val context = ExecutionContext(evaluationContext, frameProxy)
|
||||||
|
|
||||||
|
val debugMetadataKtType = context.findClass(DEBUG_METADATA_KT) as? ClassType ?: return null
|
||||||
|
|
||||||
|
val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType)
|
||||||
|
return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
|
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
|
||||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val thisObject = frameProxy.thisObject() ?: return null
|
val thisObject = context.frameProxy.thisObject() ?: return null
|
||||||
val thisType = thisObject.referenceType()
|
val thisType = thisObject.referenceType()
|
||||||
|
|
||||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||||
@@ -95,24 +73,25 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
|
|||||||
return collectFrames(thisObject)
|
return collectFrames(thisObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
||||||
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val frameProxy = context.frameProxy
|
||||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||||
|
|
||||||
return collectFrames(continuation)
|
return collectFrames(continuation)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
||||||
val frames = mutableListOf<StackFrameItem>()
|
val frames = mutableListOf<StackFrameItem>()
|
||||||
collectFramesRecursively(continuation, frames)
|
collectFramesRecursively(continuation, frames)
|
||||||
return frames
|
return frames
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
private fun AsyncStackTraceContext.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
||||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
val continuationType = continuation.referenceType() as? ClassType ?: return
|
||||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||||
|
|
||||||
@@ -128,87 +107,74 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
|
|||||||
collectFramesRecursively(completion, consumer)
|
collectFramesRecursively(completion, consumer)
|
||||||
}
|
}
|
||||||
|
|
||||||
private open class ContextBase(val suspendContext: SuspendContextImpl, val frameProxy: StackFrameProxyImpl) {
|
private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? {
|
||||||
val virtualMachine: VirtualMachineProxyImpl
|
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
||||||
get() = frameProxy.virtualMachine
|
"getStackTraceElement",
|
||||||
|
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
||||||
|
).firstOrNull() ?: return null
|
||||||
|
|
||||||
val debugProcess: DebugProcessImpl
|
val args = listOf(continuation)
|
||||||
get() = suspendContext.debugProcess
|
|
||||||
|
|
||||||
val evaluationContext: EvaluationContextImpl by lazy { EvaluationContextImpl(suspendContext, frameProxy) }
|
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
||||||
}
|
?: return null
|
||||||
|
|
||||||
private class Context(
|
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
||||||
suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl,
|
?: return null
|
||||||
val method: Method, private val debugMetadataKtType: ClassType
|
|
||||||
) : ContextBase(suspendContext, frameProxy) {
|
|
||||||
fun getLocation(continuation: ObjectReference): Location? {
|
|
||||||
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
|
||||||
"getStackTraceElement",
|
|
||||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
|
||||||
).firstOrNull() ?: return null
|
|
||||||
|
|
||||||
val args = listOf(continuation)
|
fun getValue(name: String, desc: String): Value? {
|
||||||
|
val method = stackTraceElementType.methodsByName(name, desc).single()
|
||||||
val stackTraceElement = debugProcess
|
return context.invokeMethod(stackTraceElement, method, emptyList())
|
||||||
.invokeMethod(evaluationContext, debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
|
||||||
?: return null
|
|
||||||
|
|
||||||
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
|
||||||
?: return null
|
|
||||||
|
|
||||||
fun getValue(name: String, desc: String): Value? {
|
|
||||||
val method = stackTraceElementType.methodsByName(name, desc).single()
|
|
||||||
return debugProcess.invokeMethod(evaluationContext, stackTraceElement, method, emptyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
|
||||||
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
|
||||||
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
|
||||||
|
|
||||||
val locationClass = classByName(className) ?: return null
|
|
||||||
return GeneratedLocation(suspendContext.debugProcess, locationClass, methodName, lineNumber)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||||
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||||
"getSpilledVariableFieldMapping",
|
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
||||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
|
||||||
).firstOrNull() ?: return null
|
|
||||||
|
|
||||||
val args = listOf(continuation)
|
val locationClass = context.findClass(className) ?: return null
|
||||||
|
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||||
|
}
|
||||||
|
|
||||||
val rawSpilledVariables = debugProcess
|
private fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||||
.invokeMethod(evaluationContext, debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
||||||
?: return null
|
"getSpilledVariableFieldMapping",
|
||||||
|
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
||||||
|
).firstOrNull() ?: return null
|
||||||
|
|
||||||
val length = rawSpilledVariables.length() / 2
|
val args = listOf(continuation)
|
||||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
|
||||||
|
|
||||||
for (index in 0 until length) {
|
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
||||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
?: return null
|
||||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
|
||||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
|
||||||
|
|
||||||
val project = suspendContext.debugProcess.project
|
val length = rawSpilledVariables.length() / 2
|
||||||
|
val spilledVariables = ArrayList<XNamedValue>(length)
|
||||||
|
|
||||||
val valueDescriptor = object : ValueDescriptorImpl(project) {
|
for (index in 0 until length) {
|
||||||
override fun calcValueName() = variableName
|
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
||||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
||||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
||||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
spilledVariables += JavaValue.create(
|
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
||||||
null,
|
override fun calcValueName() = variableName
|
||||||
valueDescriptor,
|
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
||||||
evaluationContext,
|
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||||
suspendContext.debugProcess.xdebugProcess!!.nodeManager,
|
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return spilledVariables
|
spilledVariables += JavaValue.create(
|
||||||
|
null,
|
||||||
|
valueDescriptor,
|
||||||
|
context.evaluationContext,
|
||||||
|
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return spilledVariables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class AsyncStackTraceContext(
|
||||||
|
val context: ExecutionContext,
|
||||||
|
val method: Method,
|
||||||
|
val debugMetadataKtType: ClassType
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user