Use caching 'findClass()' instead of raw 'Class.forName()' call in async stack trace provider (KT-30268)

This commit is contained in:
Yan Zhulanow
2019-03-13 18:42:38 +03:00
parent b2a47027fb
commit e79ee1ba8e
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.DebuggerContext
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.JavaValue
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.jdi.GeneratedLocation
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
import com.intellij.xdebugger.frame.XNamedValue
import com.sun.jdi.*
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.utils.addToStdlib.firstIsInstanceOrNull
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
private companion object {
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? {
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
return type
@@ -73,19 +48,22 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
if (currentThread == null || !currentThread.isSuspended || !currentThread.isAtBreakpoint) {
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)
return context.getAsyncStackTraceForSuspendLambda() ?: context.getAsyncStackTraceForSuspendFunction()
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
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;") {
return null
}
val thisObject = frameProxy.thisObject() ?: return null
val thisObject = context.frameProxy.thisObject() ?: return null
val thisType = thisObject.referenceType()
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
@@ -95,24 +73,25 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
return collectFrames(thisObject)
}
private fun Context.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
return null
}
val frameProxy = context.frameProxy
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
return collectFrames(continuation)
}
private fun Context.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
val frames = mutableListOf<StackFrameItem>()
collectFramesRecursively(continuation, 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 baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
@@ -128,87 +107,74 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
collectFramesRecursively(completion, consumer)
}
private open class ContextBase(val suspendContext: SuspendContextImpl, val frameProxy: StackFrameProxyImpl) {
val virtualMachine: VirtualMachineProxyImpl
get() = frameProxy.virtualMachine
private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? {
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
"getStackTraceElement",
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
).firstOrNull() ?: return null
val debugProcess: DebugProcessImpl
get() = suspendContext.debugProcess
val args = listOf(continuation)
val evaluationContext: EvaluationContextImpl by lazy { EvaluationContextImpl(suspendContext, frameProxy) }
}
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
?: return null
private class Context(
suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl,
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 stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
?: return null
val args = listOf(continuation)
val stackTraceElement = debugProcess
.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 getValue(name: String, desc: String): Value? {
val method = stackTraceElementType.methodsByName(name, desc).single()
return context.invokeMethod(stackTraceElement, method, emptyList())
}
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
"getSpilledVariableFieldMapping",
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
).firstOrNull() ?: return null
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 args = listOf(continuation)
val locationClass = context.findClass(className) ?: return null
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
}
val rawSpilledVariables = debugProcess
.invokeMethod(evaluationContext, debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
?: return null
private fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
"getSpilledVariableFieldMapping",
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
).firstOrNull() ?: return null
val length = rawSpilledVariables.length() / 2
val spilledVariables = ArrayList<XNamedValue>(length)
val args = listOf(continuation)
for (index in 0 until length) {
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
?: return null
val project = suspendContext.debugProcess.project
val length = rawSpilledVariables.length() / 2
val spilledVariables = ArrayList<XNamedValue>(length)
val valueDescriptor = object : ValueDescriptorImpl(project) {
override fun calcValueName() = variableName
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
override fun getDescriptorEvaluation(context: DebuggerContext?) =
throw EvaluateException("Spilled variable evaluation is not supported")
}
for (index in 0 until length) {
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
spilledVariables += JavaValue.create(
null,
valueDescriptor,
evaluationContext,
suspendContext.debugProcess.xdebugProcess!!.nodeManager,
false
)
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
override fun calcValueName() = variableName
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
override fun getDescriptorEvaluation(context: DebuggerContext?) =
throw EvaluateException("Spilled variable evaluation is not supported")
}
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
)
}